<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
	<id>https://wiki.gwtools.ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Bas</id>
	<title>Bas Wiki - Вклад [ru]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.gwtools.ru/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Bas"/>
	<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/Bas"/>
	<updated>2026-11-03T19:53:39Z</updated>
	<subtitle>Вклад</subtitle>
	<generator>MediaWiki 1.46.2</generator>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=696</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=696"/>
		<updated>2026-10-31T15:55:32Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 7. Управление доменами, ящиками, паролями и алиасами */ Отдельные блоки: добавление и удаление алиаса, установка и смена пароля; remove-alias снимает право отправки.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет → TCP 25 / STARTTLS → Exim&lt;br /&gt;
Клиент   → TCP 465 TLS или 587 STARTTLS + AUTH → Exim&lt;br /&gt;
Exim     → LMTP Unix socket → Dovecot → /opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim     → AUTH Unix socket → Dovecot → файловый каталог пользователей&lt;br /&gt;
Клиент   → TCP 993 TLS → Dovecot&lt;br /&gt;
SnappyMail (необязательно) → те же IMAPS и SMTP AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
sysrc hostname=mail.example.org&lt;br /&gt;
hostname mail.example.org&lt;br /&gt;
sysrc defaultrouter=198.51.100.1&lt;br /&gt;
# Только если в работающей таблице отсутствует default route:&lt;br /&gt;
route add default 198.51.100.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo /dev/da1 | awk &#039;{print $3}&#039;)&lt;br /&gt;
test -n &amp;quot;$disk_bytes&amp;quot; &amp;amp;&amp;amp; test &amp;quot;$disk_bytes&amp;quot; -gt 0 || exit 1&lt;br /&gt;
cmp -s -n &amp;quot;$disk_bytes&amp;quot; /dev/da1 /dev/zero&lt;br /&gt;
printf &#039;cmp status: %s\n&#039; &amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
pkg update&lt;br /&gt;
pkg install -n exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
# После проверки плана:&lt;br /&gt;
pkg install -y exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
sysrc exim_enable=NO dovecot_enable=NO&lt;br /&gt;
sysrc sendmail_enable=NONE sendmail_submit_enable=NO&lt;br /&gt;
sysrc sendmail_outbound_enable=NO sendmail_msp_queue_enable=NO&lt;br /&gt;
service sendmail onestop&lt;br /&gt;
/usr/local/sbin/exim -bV&lt;br /&gt;
/usr/local/sbin/dovecot --version&lt;br /&gt;
pw usershow mailnull&lt;br /&gt;
pw groupshow mail&lt;br /&gt;
pw groupshow dovecot&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
=== 7.1. Утилита mail-user и первоначальное наполнение ===&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#x27;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Alias&amp;amp;#32;removal&amp;amp;#32;only:&amp;amp;#32;no&amp;amp;#32;mailbox/domain&amp;amp;#32;deletion,&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;or&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#x27;%s\n&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-k&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;#32;|&amp;amp;#32;remove-alias&amp;amp;#32;ALIAS&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;remove-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;remove-alias&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;ALIAS&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#x27;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#x27;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#x27;&amp;amp;#32;&amp;amp;#x27;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#x27;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#x27;exit&amp;amp;#32;1&amp;amp;#x27;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#x27;%s&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#x27;[:upper:]&amp;amp;#x27;&amp;amp;#32;&amp;amp;#x27;[:lower:]&amp;amp;#x27;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;&amp;amp;#x27;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#x27;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#x27;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#x27;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#x27;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#x27;&amp;amp;#32;&amp;amp;#x27;&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#x27;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#x27;%s\n&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#x27;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#x27;%s:%s:%s:%s::%s::\n&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#x27;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#x27;%s\n&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#x27;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#x27;%s:&amp;amp;#32;%s\n&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#x27;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;remove-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_alias_address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$remove_alias_address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$remove_alias_address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;mailbox,&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;alias.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$remove_alias_address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;alias.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$remove_alias_address&amp;amp;quot;&amp;amp;#32;&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;print;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address&amp;amp;#32;=&amp;amp;#32;$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;address)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(address&amp;amp;#32;!=&amp;amp;#32;key)&amp;amp;#32;print&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#x27;Alias&amp;amp;#32;removed:&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#x27;&amp;amp;#32;&amp;amp;quot;$remove_alias_address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#x27;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый &amp;lt;code&amp;gt;add-user&amp;lt;/code&amp;gt; запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Команды ниже предназначены для последующего обслуживания через SSH. Все адреса — примеры; замените их своими. Если сессия уже открыта от root, &amp;lt;code&amp;gt;sudo&amp;lt;/code&amp;gt; можно опустить.&lt;br /&gt;
&lt;br /&gt;
=== 7.2. Добавление алиаса ===&lt;br /&gt;
Первый адрес — новый алиас, второй — существующий основной ящик того же домена:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo /usr/local/sbin/mail-user add-alias support@example.org alice@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Письма на &amp;lt;code&amp;gt;support@example.org&amp;lt;/code&amp;gt; будут доставляться в &amp;lt;code&amp;gt;alice@example.org&amp;lt;/code&amp;gt;. Команда также разрешает владельцу ящика отправлять почту с адреса алиаса. Адрес должен быть свободен; цепочки алиасов и пересылка в другой домен не поддерживаются. Для каждого домена алиас добавляется отдельно.&lt;br /&gt;
&lt;br /&gt;
Проверить маршрут доставки без отправки письма:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo /usr/local/sbin/exim -bt support@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В результате должен быть указан основной ящик. Алиас не получает собственного пароля и не используется как отдельный IMAP-логин; вход в SnappyMail и почтовый клиент выполняется основным полным адресом.&lt;br /&gt;
&lt;br /&gt;
=== 7.3. Удаление алиаса ===&lt;br /&gt;
Укажите только удаляемый адрес алиаса:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo /usr/local/sbin/mail-user remove-alias support@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команда удаляет запись алиаса и право отправки с этого адреса из &amp;lt;code&amp;gt;sender_owners&amp;lt;/code&amp;gt;, согласованно пересоздавая карты Exim под общей блокировкой. Основной ящик, его пароль и уже полученные письма сохраняются. Основной адрес ящика этой командой удалить нельзя; для отсутствующего алиаса возвращается ошибка &amp;lt;code&amp;gt;No such alias.&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Проверить результат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo /usr/local/sbin/exim -bt support@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Для удалённого алиаса ожидается &amp;lt;code&amp;gt;Unknown local recipient&amp;lt;/code&amp;gt; (код завершения может быть ненулевым). Новые письма на этот адрес больше не принимаются. Перезапуск Exim и Dovecot не требуется; проверку выполняйте новым SMTP-сеансом. Не удаляйте только строку из &amp;lt;code&amp;gt;aliases&amp;lt;/code&amp;gt; вручную: право отправки в &amp;lt;code&amp;gt;sender_owners&amp;lt;/code&amp;gt; должно быть снято одновременно. Если в SnappyMail вручную добавлена личность отправителя с удалённым адресом, удалите её из настроек клиента.&lt;br /&gt;
&lt;br /&gt;
=== 7.4. Установка и смена пароля ===&lt;br /&gt;
При создании нового основного ящика пароль устанавливается командой:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo /usr/local/sbin/mail-user add-user bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Домен должен быть добавлен заранее, адрес — свободен. Если ящик уже существует, используйте смену пароля:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo /usr/local/sbin/mail-user set-password alice@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Введите новый пароль и повторите его в запросе терминала. Ввод не отображается. Старый пароль администратору знать не требуется. Не передавайте пароль аргументом команды и не вставляйте его в историю shell. Для команды указывается полный основной адрес &amp;lt;code&amp;gt;user@domain&amp;lt;/code&amp;gt;; у алиаса своего пароля нет. Ящики с одинаковой локальной частью в разных доменах имеют независимые пароли.&lt;br /&gt;
&lt;br /&gt;
При отключённом &amp;lt;code&amp;gt;auth_cache_size&amp;lt;/code&amp;gt; (значение &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; в текущей настройке) новый пароль действует для новых входов IMAP и SMTP AUTH без перезапуска служб. Обновите сохранённый пароль в SnappyMail и почтовых клиентах и войдите заново. Уже открытые IMAP-сеансы при смене пароля автоматически не закрываются. Если кеш аутентификации будет включён, после смены пароля его необходимо очистить для соответствующего пользователя.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only review and explicit training of IMAPSieve feedback.&lt;br /&gt;
&lt;br /&gt;
No command trains automatically. list/show are local reads. learn ID spam|ham&lt;br /&gt;
is the only command that invokes rspamc. reject ID archives without training.&lt;br /&gt;
Queue root and processed must be root-owned, non-writable by mail users;&lt;br /&gt;
pending is separately owned by vmail, mode 0700. Archived originals remain in&lt;br /&gt;
processed/ID/source; only root-owned snapshots are used for training or review.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from contextlib import contextmanager&lt;br /&gt;
from email import policy&lt;br /&gt;
from email.parser import BytesHeaderParser&lt;br /&gt;
import hashlib&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import shutil&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
&lt;br /&gt;
spec = importlib.util.spec_from_file_location(&amp;quot;feedback_collector&amp;quot;, Path(__file__).with_name(&amp;quot;feedback.py&amp;quot;))&lt;br /&gt;
collector = importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE = collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError = collector.FeedbackError&lt;br /&gt;
ID_RE = re.compile(r&amp;quot;[0-9a-f]{32}\Z&amp;quot;)&lt;br /&gt;
DEFAULT_ROOT = &amp;quot;/opt/mail/rspamd-feedback&amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE = &amp;quot;/usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
HEADERS = (&amp;quot;From&amp;quot;, &amp;quot;To&amp;quot;, &amp;quot;Date&amp;quot;, &amp;quot;Subject&amp;quot;, &amp;quot;Message-ID&amp;quot;, &amp;quot;X-Spam-Status&amp;quot;, &amp;quot;X-Spam-Score&amp;quot;, &amp;quot;X-Rspamd-Action&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def emit(value):&lt;br /&gt;
    print(json.dumps(value, ensure_ascii=True, sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def require_root():&lt;br /&gt;
    if not hasattr(os, &amp;quot;geteuid&amp;quot;) or os.geteuid() != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;this administrative command requires root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_id(event_id):&lt;br /&gt;
    if not ID_RE.fullmatch(event_id):&lt;br /&gt;
        raise FeedbackError(&amp;quot;ID must be exactly 32 lowercase hexadecimal characters&amp;quot;)&lt;br /&gt;
    return event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Directory:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Nofollow directory descriptor on FreeBSD; portable fallback for local tests.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, path):&lt;br /&gt;
        self.path = Path(path)&lt;br /&gt;
        info = self.path.lstat()&lt;br /&gt;
        if not stat.S_ISDIR(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;expected a real directory&amp;quot;)&lt;br /&gt;
        self.fd = None&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            self.fd = os.open(self.path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
    def __enter__(self):&lt;br /&gt;
        return self&lt;br /&gt;
&lt;br /&gt;
    def __exit__(self, *args):&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
    def read(self, name, limit):&lt;br /&gt;
        if &amp;quot;/&amp;quot; in name or &amp;quot;\\&amp;quot; in name or name in (&amp;quot;.&amp;quot;, &amp;quot;..&amp;quot;):&lt;br /&gt;
            raise FeedbackError(&amp;quot;invalid internal filename&amp;quot;)&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=self.fd)&lt;br /&gt;
        else:&lt;br /&gt;
            path = self.path / name&lt;br /&gt;
            if path.is_symlink():&lt;br /&gt;
                raise FeedbackError(&amp;quot;symlink is not an event file&amp;quot;)&lt;br /&gt;
            fd = os.open(path, os.O_RDONLY | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0))&lt;br /&gt;
        with os.fdopen(fd, &amp;quot;rb&amp;quot;) as source:&lt;br /&gt;
            info = os.fstat(source.fileno())&lt;br /&gt;
            if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_size &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file is not a bounded, single-link regular file&amp;quot;)&lt;br /&gt;
            result = source.read(limit + 1)&lt;br /&gt;
            if len(result) &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file exceeded the size limit&amp;quot;)&lt;br /&gt;
            return result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def read_event(directory, event_id):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    # On POSIX open the event relative to the already opened parent descriptor.&lt;br /&gt;
    # The parent path is deployment-owned, and a symlink event is rejected.&lt;br /&gt;
    with Directory(Path(directory) / event_id) as entry:&lt;br /&gt;
        metadata = json.loads(entry.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
        message = entry.read(&amp;quot;message.eml&amp;quot;, MAX_MESSAGE)&lt;br /&gt;
    if not isinstance(metadata, dict) or not isinstance(metadata.get(&amp;quot;user&amp;quot;), str):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                             metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
    if metadata.get(&amp;quot;id&amp;quot;) != event_id or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    if metadata.get(&amp;quot;bytes&amp;quot;) != len(message) or metadata.get(&amp;quot;sha256&amp;quot;) != hashlib.sha256(message).hexdigest():&lt;br /&gt;
        raise FeedbackError(&amp;quot;event content does not match its metadata&amp;quot;)&lt;br /&gt;
    return metadata, message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def write_private(path, value, *, json_value=False):&lt;br /&gt;
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0)&lt;br /&gt;
    fd = os.open(path, flags, 0o600)&lt;br /&gt;
    with os.fdopen(fd, &amp;quot;wb&amp;quot;) as output:&lt;br /&gt;
        if json_value:&lt;br /&gt;
            value = (json.dumps(value, ensure_ascii=True, sort_keys=True) + &amp;quot;\n&amp;quot;).encode()&lt;br /&gt;
        output.write(value)&lt;br /&gt;
        output.flush()&lt;br /&gt;
        os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def update_receipt(directory, receipt):&lt;br /&gt;
    temporary = directory / &amp;quot;.admin-new.json&amp;quot;&lt;br /&gt;
    write_private(temporary, receipt, json_value=True)&lt;br /&gt;
    os.replace(temporary, directory / &amp;quot;admin.json&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_root_directory(path):&lt;br /&gt;
    with Directory(path):&lt;br /&gt;
        info = Path(path).stat()&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;administrative directories must be root-owned and not group/other writable&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def admin_lock(root):&lt;br /&gt;
    check_root_directory(root)&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        processed.mkdir(mode=0o700)&lt;br /&gt;
    check_root_directory(processed)&lt;br /&gt;
    fd = os.open(root / &amp;quot;.admin.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(fd, fcntl.LOCK_EX)&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def receipt_for(directory):&lt;br /&gt;
    with Directory(directory) as entry:&lt;br /&gt;
        return json.loads(entry.read(&amp;quot;admin.json&amp;quot;, 16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def find_prior(root, sha256):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return None&lt;br /&gt;
    for entry in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
            continue&lt;br /&gt;
        try:&lt;br /&gt;
            receipt = receipt_for(entry)&lt;br /&gt;
        except (OSError, ValueError, FeedbackError):&lt;br /&gt;
            # A damaged journal must never silently allow repeated training.&lt;br /&gt;
            raise FeedbackError(&amp;quot;processed journal is incomplete; inspect it before more training&amp;quot;)&lt;br /&gt;
        if receipt.get(&amp;quot;sha256&amp;quot;) == sha256 and receipt.get(&amp;quot;status&amp;quot;) in (&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learned&amp;quot;, &amp;quot;learn_uncertain&amp;quot;):&lt;br /&gt;
            return receipt&lt;br /&gt;
    return None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def prepare_archive(root, event_id, metadata, message, status, label=None, reason=None):&lt;br /&gt;
    if shutil.disk_usage(root).free &amp;lt; collector.MIN_FREE_BYTES + len(message) + 32768:&lt;br /&gt;
        raise FeedbackError(&amp;quot;insufficient free disk space to preserve the reviewed snapshot&amp;quot;)&lt;br /&gt;
    archive = root / &amp;quot;processed&amp;quot; / event_id&lt;br /&gt;
    archive.mkdir(mode=0o700)&lt;br /&gt;
    receipt = {&amp;quot;schema&amp;quot;: 1, &amp;quot;id&amp;quot;: event_id, &amp;quot;sha256&amp;quot;: metadata[&amp;quot;sha256&amp;quot;],&lt;br /&gt;
               &amp;quot;reviewed_utc&amp;quot;: collector.utc_now(), &amp;quot;status&amp;quot;: &amp;quot;preparing&amp;quot;,&lt;br /&gt;
               &amp;quot;selected_class&amp;quot;: label, &amp;quot;reason&amp;quot;: reason}&lt;br /&gt;
    write_private(archive / &amp;quot;admin.json&amp;quot;, receipt, json_value=True)&lt;br /&gt;
    write_private(archive / &amp;quot;message.eml&amp;quot;, message)&lt;br /&gt;
    write_private(archive / &amp;quot;metadata.json&amp;quot;, json.dumps(metadata, ensure_ascii=True).encode())&lt;br /&gt;
    # Keep the complete original event. Never traverse or execute files in source/.&lt;br /&gt;
    os.rename(root / &amp;quot;pending&amp;quot; / event_id, archive / &amp;quot;source&amp;quot;)&lt;br /&gt;
    receipt[&amp;quot;status&amp;quot;] = status&lt;br /&gt;
    update_receipt(archive, receipt)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;pending&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;processed&amp;quot;)&lt;br /&gt;
    return archive, receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def run_learning(message, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    executable = Path(executable)&lt;br /&gt;
    if not executable.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be an absolute path&amp;quot;)&lt;br /&gt;
    command = [str(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be an absolute regular file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be root-owned, mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        # rspamc reads the file itself; the secret never appears in argv or logs.&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;learn_&amp;quot; + label)&lt;br /&gt;
    # No -u/-d: one shared Bayes classifier. No shell and no automatic retry.&lt;br /&gt;
    return subprocess.run(command, input=message, stdout=subprocess.DEVNULL,&lt;br /&gt;
                          stderr=subprocess.DEVNULL, timeout=timeout, check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def learn_event(root, event_id, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid training class&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        prior = find_prior(root, metadata[&amp;quot;sha256&amp;quot;])&lt;br /&gt;
        if prior:&lt;br /&gt;
            raise FeedbackError(&amp;quot;this content has a prior learned or uncertain event; inspect it and reject the duplicate&amp;quot;)&lt;br /&gt;
        archive, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;learning&amp;quot;, label)&lt;br /&gt;
        try:&lt;br /&gt;
            rc = run_learning(message, label, executable=executable, controller=controller,&lt;br /&gt;
                              password_file=password_file, timeout=timeout)&lt;br /&gt;
        except subprocess.TimeoutExpired:&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client timeout; server may have learned; do not automatically retry&amp;quot;&lt;br /&gt;
        except (OSError, FeedbackError):&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_failed&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client could not start or local configuration was invalid&amp;quot;&lt;br /&gt;
        else:&lt;br /&gt;
            receipt[&amp;quot;returncode&amp;quot;] = rc&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if rc == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            if rc:&lt;br /&gt;
                receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; inspect server before any retry&amp;quot;&lt;br /&gt;
        receipt[&amp;quot;finished_utc&amp;quot;] = collector.utc_now()&lt;br /&gt;
        update_receipt(archive, receipt)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def reject_event(root, event_id, reason=&amp;quot;review rejected&amp;quot;):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if len(reason) &amp;gt; 500:&lt;br /&gt;
        raise FeedbackError(&amp;quot;reason is too long&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        _, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;rejected&amp;quot;, reason=reason)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def list_events(root, state, limit):&lt;br /&gt;
    count = 0&lt;br /&gt;
    for name in ((&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;) if state == &amp;quot;all&amp;quot; else (state,)):&lt;br /&gt;
        directory = root / name&lt;br /&gt;
        if not directory.exists():&lt;br /&gt;
            continue&lt;br /&gt;
        with Directory(directory):&lt;br /&gt;
            for entry in directory.iterdir():&lt;br /&gt;
                if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                if count &amp;gt;= limit:&lt;br /&gt;
                    emit({&amp;quot;notice&amp;quot;: &amp;quot;limit reached&amp;quot;, &amp;quot;limit&amp;quot;: limit})&lt;br /&gt;
                    return&lt;br /&gt;
                try:&lt;br /&gt;
                    with Directory(entry) as item:&lt;br /&gt;
                        metadata = json.loads(item.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    row = {key: metadata.get(key) for key in (&amp;quot;id&amp;quot;, &amp;quot;created_utc&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;user&amp;quot;, &amp;quot;source&amp;quot;, &amp;quot;destination&amp;quot;, &amp;quot;bytes&amp;quot;)}&lt;br /&gt;
                    row[&amp;quot;state&amp;quot;] = name&lt;br /&gt;
                    if name == &amp;quot;processed&amp;quot;:&lt;br /&gt;
                        row[&amp;quot;review&amp;quot;] = receipt_for(entry)&lt;br /&gt;
                    emit(row)&lt;br /&gt;
                except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                    emit({&amp;quot;id&amp;quot;: entry.name, &amp;quot;state&amp;quot;: name, &amp;quot;error&amp;quot;: &amp;quot;invalid event; manual inspection required&amp;quot;})&lt;br /&gt;
                count += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def show_event(root, event_id, state, path_only):&lt;br /&gt;
    metadata, message = read_event(root / state, check_id(event_id))&lt;br /&gt;
    if path_only:&lt;br /&gt;
        emit({&amp;quot;id&amp;quot;: event_id, &amp;quot;message_path&amp;quot;: str(root / state / event_id / &amp;quot;message.eml&amp;quot;)})&lt;br /&gt;
        return&lt;br /&gt;
    headers = BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message), 262144)])&lt;br /&gt;
    emit({&amp;quot;metadata&amp;quot;: metadata, &amp;quot;headers&amp;quot;: {name: [str(v)[:2000] for v in headers.get_all(name, [])[:10]] for name in HEADERS}})&lt;br /&gt;
    if state == &amp;quot;processed&amp;quot;:&lt;br /&gt;
        emit({&amp;quot;review&amp;quot;: receipt_for(root / state / event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def warn_processed_storage(root):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return&lt;br /&gt;
    total = count = 0&lt;br /&gt;
    for event in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(event.name) or event.is_symlink() or not event.is_dir():&lt;br /&gt;
            continue&lt;br /&gt;
        count += 1&lt;br /&gt;
        for prefix in (event, event / &amp;quot;source&amp;quot;):&lt;br /&gt;
            if prefix.is_symlink():&lt;br /&gt;
                continue&lt;br /&gt;
            for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    info = (prefix / name).lstat()&lt;br /&gt;
                    if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                        total += info.st_size&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    pass&lt;br /&gt;
    if total &amp;gt;= collector.MAX_PENDING_BYTES or count &amp;gt;= collector.MAX_PENDING_EVENTS:&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: WARNING processed archive reached 512 MiB or 1024 events; review retention manually; nothing deleted&amp;quot;, file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=DEFAULT_ROOT)&lt;br /&gt;
    sub = parser.add_subparsers(dest=&amp;quot;command&amp;quot;, required=True)&lt;br /&gt;
    listing = sub.add_parser(&amp;quot;list&amp;quot;, help=&amp;quot;JSON lines, metadata only&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;, &amp;quot;all&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--limit&amp;quot;, type=int, default=100)&lt;br /&gt;
    showing = sub.add_parser(&amp;quot;show&amp;quot;, help=&amp;quot;selected headers only; never dumps the body&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--path&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning = sub.add_parser(&amp;quot;learn&amp;quot;, help=&amp;quot;explicitly train one reviewed event, then archive it&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;label&amp;quot;, choices=(&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;))&lt;br /&gt;
    learning.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--password-file&amp;quot;, default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
                          help=&amp;quot;root-only plaintext secret file; use --no-password only for an intentionally trusted local controller&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--no-password&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    rejecting = sub.add_parser(&amp;quot;reject&amp;quot;, help=&amp;quot;archive one event without training or deleting it&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;--reason&amp;quot;, default=&amp;quot;review rejected&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    try:&lt;br /&gt;
        require_root()&lt;br /&gt;
        root = Path(args.queue_root)&lt;br /&gt;
        if not root.is_absolute():&lt;br /&gt;
            raise FeedbackError(&amp;quot;queue root must be absolute&amp;quot;)&lt;br /&gt;
        check_root_directory(root)&lt;br /&gt;
        warn_processed_storage(root)&lt;br /&gt;
        if args.command == &amp;quot;list&amp;quot;:&lt;br /&gt;
            if not 1 &amp;lt;= args.limit &amp;lt;= 10000:&lt;br /&gt;
                raise FeedbackError(&amp;quot;limit must be between 1 and 10000&amp;quot;)&lt;br /&gt;
            list_events(root, args.state, args.limit)&lt;br /&gt;
        elif args.command == &amp;quot;show&amp;quot;:&lt;br /&gt;
            show_event(root, args.id, args.state, args.path)&lt;br /&gt;
        elif args.command == &amp;quot;reject&amp;quot;:&lt;br /&gt;
            emit(reject_event(root, args.id, args.reason))&lt;br /&gt;
        else:&lt;br /&gt;
            if not 0 &amp;lt; args.timeout &amp;lt;= 300:&lt;br /&gt;
                raise FeedbackError(&amp;quot;timeout must be between 0 and 300 seconds&amp;quot;)&lt;br /&gt;
            result = learn_event(root, args.id, args.label, executable=args.rspamc_bin,&lt;br /&gt;
                                 controller=args.controller, password_file=None if args.no_password else args.password_file,&lt;br /&gt;
                                 timeout=args.timeout)&lt;br /&gt;
            emit(result)&lt;br /&gt;
            return 0 if result[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot; else 2&lt;br /&gt;
        return 0&lt;br /&gt;
    except (FeedbackError, OSError, ValueError, TypeError):&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: operation refused or failed; inspect configuration/event locally&amp;quot;, file=sys.stderr)&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    raise SystemExit(main())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only background learning from the bounded IMAPSieve feedback queue.&lt;br /&gt;
&lt;br /&gt;
The collector remains a local spool writer. This process owns controller&lt;br /&gt;
credentials, learns only validated INBOX/Junk events, preserves every original,&lt;br /&gt;
and never retries a training operation with an uncertain result. Run foreground&lt;br /&gt;
under FreeBSD daemon(8), or use --once for one bounded batch.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import signal&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
import threading&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
_spec = importlib.util.spec_from_file_location(&amp;quot;feedback_worker_admin&amp;quot;, Path(__file__).with_name(&amp;quot;feedback-admin.py&amp;quot;))&lt;br /&gt;
admin = importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError = admin.FeedbackError&lt;br /&gt;
SHA_RE = re.compile(r&amp;quot;[0-9a-f]{64}\Z&amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS = 20000&lt;br /&gt;
UNCERTAIN = {&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learn_uncertain&amp;quot;, &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;quarantined_ordering&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def log_event(kind, **fields):&lt;br /&gt;
    # Callers pass only fixed diagnostics, numeric counts, event IDs and classes.&lt;br /&gt;
    print(json.dumps({&amp;quot;event&amp;quot;: kind, **fields}, ensure_ascii=True, sort_keys=True), flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _timestamp_ns(value):&lt;br /&gt;
    if not isinstance(value, str):&lt;br /&gt;
        raise ValueError(&amp;quot;missing timestamp&amp;quot;)&lt;br /&gt;
    parsed = datetime.fromisoformat(value.replace(&amp;quot;Z&amp;quot;, &amp;quot;+00:00&amp;quot;))&lt;br /&gt;
    if parsed.tzinfo is None:&lt;br /&gt;
        raise ValueError(&amp;quot;timestamp must have timezone&amp;quot;)&lt;br /&gt;
    return int(parsed.timestamp() * 1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def order_events(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read bounded metadata, ordered by collector created_ns, then event ID.&lt;br /&gt;
&lt;br /&gt;
    Legacy second-resolution timestamps are permitted only when the same&lt;br /&gt;
    content has no contradictory labels at an identical timestamp. Such a tie&lt;br /&gt;
    cannot be resolved from random event IDs and is quarantined without learning.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    pending = Path(root) / &amp;quot;pending&amp;quot;&lt;br /&gt;
    rows = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    with admin.Directory(pending):&lt;br /&gt;
        with os.scandir(pending) as entries:&lt;br /&gt;
            for entry in entries:&lt;br /&gt;
                if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                count += 1&lt;br /&gt;
                if count &amp;gt; admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
                    raise FeedbackError(&amp;quot;pending queue scan bound exceeded&amp;quot;)&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(pending / entry.name) as directory:&lt;br /&gt;
                        metadata = json.loads(directory.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    if not isinstance(metadata, dict):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid metadata&amp;quot;)&lt;br /&gt;
                    metadata = dict(metadata)&lt;br /&gt;
                    if metadata.get(&amp;quot;id&amp;quot;) != entry.name or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid event identity&amp;quot;)&lt;br /&gt;
                    if not isinstance(metadata.get(&amp;quot;sha256&amp;quot;), str) or not SHA_RE.fullmatch(metadata[&amp;quot;sha256&amp;quot;]):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid digest&amp;quot;)&lt;br /&gt;
                    admin.collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                                                   metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
                    timestamp = metadata.get(&amp;quot;created_ns&amp;quot;)&lt;br /&gt;
                    if isinstance(timestamp, int) and not isinstance(timestamp, bool) and timestamp &amp;gt; 0:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = timestamp&lt;br /&gt;
                    elif &amp;quot;created_ns&amp;quot; not in metadata:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = _timestamp_ns(metadata.get(&amp;quot;created_utc&amp;quot;))&lt;br /&gt;
                    else:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = 0&lt;br /&gt;
                        metadata[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
                    rows.append(metadata)&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    # A manual administrator may have archived this candidate.&lt;br /&gt;
                    continue&lt;br /&gt;
                except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                    rows.append({&amp;quot;id&amp;quot;: entry.name, &amp;quot;_invalid&amp;quot;: True, &amp;quot;_order_ns&amp;quot;: 0})&lt;br /&gt;
    tied = {}&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
            tied.setdefault((row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;]), set()).add(row[&amp;quot;label&amp;quot;])&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;) and len(tied[(row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;])]) &amp;gt; 1:&lt;br /&gt;
            row[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
    return sorted(rows, key=lambda row: (row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;id&amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _journal(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read root-owned receipts; successful classes use durable worker sequence.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    processed = Path(root) / &amp;quot;processed&amp;quot;&lt;br /&gt;
    latest, blocked = {}, set()&lt;br /&gt;
    max_sequence = used_bytes = count = 0&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
    admin.check_root_directory(processed)&lt;br /&gt;
    with os.scandir(processed) as entries:&lt;br /&gt;
        for entry in entries:&lt;br /&gt;
            if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                continue&lt;br /&gt;
            count += 1&lt;br /&gt;
            if count &amp;gt; MAX_JOURNAL_EVENTS:&lt;br /&gt;
                raise FeedbackError(&amp;quot;processed journal scan bound exceeded&amp;quot;)&lt;br /&gt;
            directory = processed / entry.name&lt;br /&gt;
            receipt = admin.receipt_for(directory)&lt;br /&gt;
            if not isinstance(receipt, dict) or receipt.get(&amp;quot;id&amp;quot;) != entry.name:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed receipt&amp;quot;)&lt;br /&gt;
            digest = receipt.get(&amp;quot;sha256&amp;quot;)&lt;br /&gt;
            if not isinstance(digest, str) or not SHA_RE.fullmatch(digest):&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed digest&amp;quot;)&lt;br /&gt;
            sequence = receipt.get(&amp;quot;worker_sequence&amp;quot;, 0)&lt;br /&gt;
            if not isinstance(sequence, int) or isinstance(sequence, bool) or sequence &amp;lt; 0:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed sequence&amp;quot;)&lt;br /&gt;
            max_sequence = max(max_sequence, sequence)&lt;br /&gt;
            status = receipt.get(&amp;quot;status&amp;quot;)&lt;br /&gt;
            if status in UNCERTAIN:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
            if status == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                label = receipt.get(&amp;quot;selected_class&amp;quot;)&lt;br /&gt;
                if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
                    raise FeedbackError(&amp;quot;invalid learned class&amp;quot;)&lt;br /&gt;
                # Existing manual receipts predate automatic processing; the old&lt;br /&gt;
                # admin refuses further training of a SHA once it has a receipt.&lt;br /&gt;
                fallback = _timestamp_ns(receipt.get(&amp;quot;finished_utc&amp;quot;, receipt.get(&amp;quot;reviewed_utc&amp;quot;)))&lt;br /&gt;
                position = (bool(sequence), sequence if sequence else fallback, entry.name)&lt;br /&gt;
                if digest not in latest or position &amp;gt; latest[digest][0]:&lt;br /&gt;
                    latest[digest] = (position, label)&lt;br /&gt;
            # Count snapshots and preserved originals, never recursively walk&lt;br /&gt;
            # arbitrary files in the mail-user-owned source/ directory.&lt;br /&gt;
            for part in (directory, directory / &amp;quot;source&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(part) as safe:&lt;br /&gt;
                        for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                            try:&lt;br /&gt;
                                if safe.fd is not None:&lt;br /&gt;
                                    info = os.stat(name, dir_fd=safe.fd, follow_symlinks=False)&lt;br /&gt;
                                else:&lt;br /&gt;
                                    info = (part / name).lstat()&lt;br /&gt;
                                if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                                    used_bytes += info.st_size&lt;br /&gt;
                            except FileNotFoundError:&lt;br /&gt;
                                continue&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    continue&lt;br /&gt;
    return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _check_trusted_parent_chain(path):&lt;br /&gt;
    if os.name != &amp;quot;posix&amp;quot;:&lt;br /&gt;
        return&lt;br /&gt;
    for parent in Path(path).parents:&lt;br /&gt;
        link_info = parent.lstat()&lt;br /&gt;
        info = parent.stat()&lt;br /&gt;
        if (link_info.st_uid != 0 or not stat.S_ISDIR(info.st_mode)&lt;br /&gt;
                or info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;executable parent directories must be controlled by root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def resolve_executable(executable):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Allow FreeBSD package symlinks, but pin both operations to their target.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    source = Path(executable)&lt;br /&gt;
    if not source.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be absolute&amp;quot;)&lt;br /&gt;
    source_info = source.lstat()&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and source_info.st_uid != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc entry must be owned by root&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(source)&lt;br /&gt;
    target = source.resolve(strict=True)&lt;br /&gt;
    info = target.lstat()&lt;br /&gt;
    if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be a regular file&amp;quot;)&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be root-owned and not writable by mail users&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(target)&lt;br /&gt;
    return str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def controller_ready(executable, controller, password_file, timeout):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Authenticated read-only controller probe; no candidate is archived yet.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    command = [resolve_executable(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;unsafe controller password file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;controller password file must be root-owned mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;stat&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        return subprocess.run(command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,&lt;br /&gt;
                              stderr=subprocess.DEVNULL, check=False, timeout=min(timeout, 5)).returncode == 0&lt;br /&gt;
    except (subprocess.TimeoutExpired, OSError):&lt;br /&gt;
        return False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def process_pending(root, *, executable=&amp;quot;/usr/local/bin/rspamc&amp;quot;, controller=&amp;quot;127.0.0.1:11334&amp;quot;,&lt;br /&gt;
                    password_file=admin.DEFAULT_PASSWORD_FILE, timeout=30, batch_size=20,&lt;br /&gt;
                    preflight=None, learner=None, stop_event=None):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Process at most batch_size candidates. Inject probes/learner for unit tests.&lt;br /&gt;
&lt;br /&gt;
    One admin lock serializes manual and background operations. A crash after a&lt;br /&gt;
    durable learning intent leaves status=learning, which blocks this SHA on the&lt;br /&gt;
    next pass; subsequent candidates are preserved as quarantined_uncertain.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    root = Path(root)&lt;br /&gt;
    preflight = controller_ready if preflight is None else preflight&lt;br /&gt;
    if learner is None:&lt;br /&gt;
        # FreeBSD pkg installs rspamc as a root-owned versioned symlink. Resolve&lt;br /&gt;
        # it once so preflight and training invoke the same trusted executable.&lt;br /&gt;
        executable = resolve_executable(executable)&lt;br /&gt;
        learner = admin.run_learning&lt;br /&gt;
    summary = {&amp;quot;learned&amp;quot;: 0, &amp;quot;duplicates&amp;quot;: 0, &amp;quot;quarantined&amp;quot;: 0, &amp;quot;invalid&amp;quot;: 0,&lt;br /&gt;
               &amp;quot;pending&amp;quot;: 0, &amp;quot;controller_unavailable&amp;quot;: False, &amp;quot;storage_blocked&amp;quot;: False,&lt;br /&gt;
               &amp;quot;archive_warning&amp;quot;: False}&lt;br /&gt;
    with admin.admin_lock(root):&lt;br /&gt;
        events = order_events(root)&lt;br /&gt;
        summary[&amp;quot;pending&amp;quot;] = len(events)&lt;br /&gt;
        if not events:&lt;br /&gt;
            return summary&lt;br /&gt;
        latest, blocked, sequence, used_bytes, archive_count = _journal(root)&lt;br /&gt;
        summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
        if used_bytes &amp;gt;= admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
            summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        if not preflight(executable, controller, password_file, timeout):&lt;br /&gt;
            summary[&amp;quot;controller_unavailable&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        handled = 0&lt;br /&gt;
        for candidate in events:&lt;br /&gt;
            if handled &amp;gt;= batch_size or (stop_event is not None and stop_event.is_set()):&lt;br /&gt;
                break&lt;br /&gt;
            if candidate.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            event_id = candidate[&amp;quot;id&amp;quot;]&lt;br /&gt;
            if (root / &amp;quot;processed&amp;quot; / event_id).exists():&lt;br /&gt;
                # Crash before prepare_archive moved pending/ID: its root-owned&lt;br /&gt;
                # intent is authoritative. Preserve the pending original for&lt;br /&gt;
                # manual recovery and continue independent content, never retry.&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            try:&lt;br /&gt;
                metadata, message = admin.read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
            except FileNotFoundError:&lt;br /&gt;
                continue&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            # Do not act if the queued ordering/identity changed since enumeration.&lt;br /&gt;
            if any(metadata.get(key) != candidate.get(key) for key in&lt;br /&gt;
                   (&amp;quot;sha256&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;created_ns&amp;quot;, &amp;quot;created_utc&amp;quot;)):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            if used_bytes + 2 * len(message) + 32768 &amp;gt; admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            label, digest = metadata[&amp;quot;label&amp;quot;], metadata[&amp;quot;sha256&amp;quot;]&lt;br /&gt;
            if candidate.get(&amp;quot;_ordering_invalid&amp;quot;):&lt;br /&gt;
                status, reason = &amp;quot;quarantined_ordering&amp;quot;, &amp;quot;event ordering is ambiguous&amp;quot;&lt;br /&gt;
            elif digest in blocked:&lt;br /&gt;
                status, reason = &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;a prior learning operation has an uncertain result&amp;quot;&lt;br /&gt;
            elif digest in latest and latest[digest][1] == label:&lt;br /&gt;
                status, reason = &amp;quot;duplicate&amp;quot;, &amp;quot;same class as the latest successful learning operation&amp;quot;&lt;br /&gt;
            else:&lt;br /&gt;
                status, reason = &amp;quot;learning&amp;quot;, &amp;quot;automatic explicit IMAP feedback&amp;quot;&lt;br /&gt;
            try:&lt;br /&gt;
                archive, receipt = admin.prepare_archive(root, event_id, metadata, message, status, label, reason)&lt;br /&gt;
            except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                # prepare_archive writes a root journal before moving the source;&lt;br /&gt;
                # stop instead of retrying an incompletely prepared operation.&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            sequence += 1&lt;br /&gt;
            receipt.update({&amp;quot;mode&amp;quot;: &amp;quot;automatic_imap_feedback&amp;quot;, &amp;quot;worker_sequence&amp;quot;: sequence,&lt;br /&gt;
                            &amp;quot;processed_ns&amp;quot;: time.time_ns(), &amp;quot;source_created_ns&amp;quot;: candidate[&amp;quot;_order_ns&amp;quot;]})&lt;br /&gt;
            if digest in latest:&lt;br /&gt;
                receipt[&amp;quot;previous_successful_class&amp;quot;] = latest[digest][1]&lt;br /&gt;
            # Durable intent exists before the only call that may change Bayes.&lt;br /&gt;
            admin.update_receipt(archive, receipt)&lt;br /&gt;
            if status == &amp;quot;learning&amp;quot;:&lt;br /&gt;
                try:&lt;br /&gt;
                    returncode = learner(message, label, executable=executable, controller=controller,&lt;br /&gt;
                                         password_file=password_file, timeout=timeout)&lt;br /&gt;
                except (subprocess.TimeoutExpired, OSError, FeedbackError):&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    receipt[&amp;quot;error&amp;quot;] = &amp;quot;client failure; server may have learned; no automatic retry&amp;quot;&lt;br /&gt;
                else:&lt;br /&gt;
                    receipt[&amp;quot;returncode&amp;quot;] = returncode&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if returncode == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    if returncode != 0:&lt;br /&gt;
                        receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; no automatic retry&amp;quot;&lt;br /&gt;
                receipt[&amp;quot;finished_utc&amp;quot;] = admin.collector.utc_now()&lt;br /&gt;
                admin.update_receipt(archive, receipt)&lt;br /&gt;
                if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                    latest[digest] = ((True, sequence, event_id), label)&lt;br /&gt;
                    summary[&amp;quot;learned&amp;quot;] += 1&lt;br /&gt;
                else:&lt;br /&gt;
                    blocked.add(digest)&lt;br /&gt;
                    summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            elif status == &amp;quot;duplicate&amp;quot;:&lt;br /&gt;
                summary[&amp;quot;duplicates&amp;quot;] += 1&lt;br /&gt;
            else:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
                summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            handled += 1&lt;br /&gt;
            used_bytes += 2 * len(message) + 32768&lt;br /&gt;
            archive_count += 1&lt;br /&gt;
            summary[&amp;quot;pending&amp;quot;] -= 1&lt;br /&gt;
            summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
            log_event(&amp;quot;feedback_processed&amp;quot;, id=event_id, label=label, status=receipt[&amp;quot;status&amp;quot;])&lt;br /&gt;
            if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learn_uncertain&amp;quot;:&lt;br /&gt;
                # The controller may have failed after the preflight. Leave all&lt;br /&gt;
                # other candidates pending until a fresh read-only probe passes.&lt;br /&gt;
                break&lt;br /&gt;
    return summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=admin.DEFAULT_ROOT)&lt;br /&gt;
    parser.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--password-file&amp;quot;, default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
    parser.add_argument(&amp;quot;--poll&amp;quot;, type=float, default=2)&lt;br /&gt;
    parser.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    parser.add_argument(&amp;quot;--batch-size&amp;quot;, type=int, default=20)&lt;br /&gt;
    parser.add_argument(&amp;quot;--once&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    admin.require_root()&lt;br /&gt;
    root = Path(args.queue_root)&lt;br /&gt;
    if not root.is_absolute() or not 0.2 &amp;lt;= args.poll &amp;lt;= 60 or not 0 &amp;lt; args.timeout &amp;lt;= 300 or not 1 &amp;lt;= args.batch_size &amp;lt;= 100:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid worker options&amp;quot;)&lt;br /&gt;
    admin.check_root_directory(root)&lt;br /&gt;
    stopping = threading.Event()&lt;br /&gt;
    for signum in (signal.SIGTERM, signal.SIGINT):&lt;br /&gt;
        signal.signal(signum, lambda *_: stopping.set())&lt;br /&gt;
    lockfd = os.open(root / &amp;quot;.worker.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)&lt;br /&gt;
        previous_state = None&lt;br /&gt;
        while not stopping.is_set():&lt;br /&gt;
            try:&lt;br /&gt;
                summary = process_pending(root, executable=args.rspamc_bin, controller=args.controller,&lt;br /&gt;
                                          password_file=args.password_file, timeout=args.timeout,&lt;br /&gt;
                                          batch_size=args.batch_size, stop_event=stopping)&lt;br /&gt;
                state = {key: value for key, value in summary.items() if key not in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)}&lt;br /&gt;
                if state != previous_state or any(summary[key] for key in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)):&lt;br /&gt;
                    log_event(&amp;quot;worker_state&amp;quot;, **summary)&lt;br /&gt;
                previous_state = state&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 2 if summary[&amp;quot;controller_unavailable&amp;quot;] or summary[&amp;quot;storage_blocked&amp;quot;] else 0&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                if previous_state != &amp;quot;local_error&amp;quot;:&lt;br /&gt;
                    log_event(&amp;quot;worker_error&amp;quot;, reason=&amp;quot;local queue, journal or configuration failure; no automatic retry of uncertain training&amp;quot;)&lt;br /&gt;
                previous_state = &amp;quot;local_error&amp;quot;&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 1&lt;br /&gt;
            stopping.wait(args.poll)&lt;br /&gt;
        return 0&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    try:&lt;br /&gt;
        raise SystemExit(main())&lt;br /&gt;
    except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
        log_event(&amp;quot;worker_start_failed&amp;quot;, reason=&amp;quot;root, configuration or worker lock required&amp;quot;)&lt;br /&gt;
        raise SystemExit(1)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При ручном перемещении INBOX → Junk письмо автоматически становится прочитанным; прочие флаги и автоматическое обучение сохраняются. Чтобы повторить настройку, дополнить список расширений require в report-spam.sieve значением &amp;lt;code&amp;gt;&amp;quot;imap4flags&amp;quot;&amp;lt;/code&amp;gt;. Сразу после первой проверки события (после блока &amp;lt;code&amp;gt;if not allof&amp;lt;/code&amp;gt; со &amp;lt;code&amp;gt;stop;&amp;lt;/code&amp;gt;), до чтения имени пользователя и вызова collector, добавить строку &amp;lt;code&amp;gt;addflag &amp;quot;\\Seen&amp;quot;;&amp;lt;/code&amp;gt;. Это правило ограничено прежним направлением INBOX → Junk. При возврате Junk → INBOX флаг прочтения не сбрасывается. После изменения повторно скомпилировать report-spam.sieve командой ниже. [https://doc.dovecot.org/main/howto/sieve.html Расширение imap4flags в Dovecot].&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Новые письма с локальной пометкой спама автоматически доставляются в Junk прочитанными; обычная почта во Входящих получает прежние флаги. Чтобы повторить настройку, после сохранения приведённого выше spam-delivery.sieve добавить &amp;lt;code&amp;gt;&amp;quot;imap4flags&amp;quot;&amp;lt;/code&amp;gt; в список расширений require. Внутри условия &amp;lt;code&amp;gt;X-Spam-Flag: YES&amp;lt;/code&amp;gt;, непосредственно перед &amp;lt;code&amp;gt;fileinto :create &amp;quot;Junk&amp;quot;;&amp;lt;/code&amp;gt;, добавить строку &amp;lt;code&amp;gt;addflag &amp;quot;\\Seen&amp;quot;;&amp;lt;/code&amp;gt;. Действие addflag добавляет флаг прочтения и сохраняет остальные флаги письма. После изменения повторно скомпилировать spam-delivery.sieve командой ниже. Уже находящиеся в папках письма не изменяются. SMTP-отклонение спама не включается; автоматическая доставка не запускает обучение. [https://www.rfc-editor.org/rfc/rfc5232 Расширение Sieve imap4flags].&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== 16. Перенос почты с Яндекса через imapsync ==&lt;br /&gt;
&lt;br /&gt;
Однократное копирование переносит письма из существующего ящика Яндекса в существующий ящик Dovecot. Пример ниже использует &amp;lt;code&amp;gt;source@example.org&amp;lt;/code&amp;gt; как источник и &amp;lt;code&amp;gt;target@example.net&amp;lt;/code&amp;gt; на &amp;lt;code&amp;gt;mail.example.net&amp;lt;/code&amp;gt; как назначение. Замените эти значения своими. Переключать MX обратно на Яндекс для такого переноса не требуется.&lt;br /&gt;
&lt;br /&gt;
Папки источника размещаются внутри отдельной папки &amp;lt;code&amp;gt;Yandex-source&amp;lt;/code&amp;gt;. Оригиналы на Яндексе и текущая почта назначения сохраняются. Команды выполняются последовательно в одной интерактивной root-сессии SSH; после переподключения параметры &amp;lt;code&amp;gt;set --&amp;lt;/code&amp;gt; нужно задать заново.&lt;br /&gt;
&lt;br /&gt;
=== 16.1. Доступ и пакеты ===&lt;br /&gt;
&lt;br /&gt;
Разрешите IMAP в настройках почтовых программ Яндекса; для организации Яндекс 360 может потребоваться также разрешение протокола в кабинете администратора. Для источника используйте пароль приложения с доступом к Почте, для назначения — пароль своего ящика Dovecot. Если Яндекс блокирует IMAP и разрешить его нельзя, imapsync не сможет прочитать письма.&lt;br /&gt;
&lt;br /&gt;
Проверьте, что &amp;lt;code&amp;gt;/opt&amp;lt;/code&amp;gt; смонтирован как UFS, целевой ящик уже существует и имя &amp;lt;code&amp;gt;mail.example.net&amp;lt;/code&amp;gt; соответствует действительному сертификату почтового сервера. Импорт выполняется в обычное хранилище IMAP; файловый каталог пользователей Exim/Dovecot менять не требуется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
sudo pkg install imapsync&lt;br /&gt;
sudo /bin/sh&lt;br /&gt;
umask 077&lt;br /&gt;
install -d -o root -g wheel -m 0700 /opt/mail/migration/yandex-source&lt;br /&gt;
install -d -o root -g wheel -m 0700 /opt/mail/migration/yandex-source/cache /opt/mail/migration/yandex-source/log&lt;br /&gt;
cd /opt/mail/migration/yandex-source&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 16.2. Ввести пароли без отображения ===&lt;br /&gt;
&lt;br /&gt;
Следующие команды запрашивают пароли в терминале и записывают их в новые файлы. Пароли не попадают в аргументы процессов, переменные окружения или историю shell. С установленной выше маской файлы имеют права &amp;lt;code&amp;gt;0600&amp;lt;/code&amp;gt;, каталог — &amp;lt;code&amp;gt;0700&amp;lt;/code&amp;gt;. Режим создания &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; отказывается перезаписывать существующие файлы; не запускайте команды повторно, если пароли уже введены.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
python3.12 -c &#039;import getpass, os, sys; assert os.geteuid() == 0 and sys.stdin.isatty(); p = getpass.getpass(&amp;quot;Пароль приложения Яндекса: &amp;quot;); assert p and not any(c in p for c in &amp;quot;\x00\r\n&amp;quot;); f = open(&amp;quot;source.pass&amp;quot;, &amp;quot;x&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;); f.write(p + &amp;quot;\n&amp;quot;); f.close()&#039;&lt;br /&gt;
python3.12 -c &#039;import getpass, os, sys; assert os.geteuid() == 0 and sys.stdin.isatty(); p = getpass.getpass(&amp;quot;Пароль целевого ящика: &amp;quot;); assert p and not any(c in p for c in &amp;quot;\x00\r\n&amp;quot;); f = open(&amp;quot;target.pass&amp;quot;, &amp;quot;x&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;); f.write(p + &amp;quot;\n&amp;quot;); f.close()&#039;&lt;br /&gt;
stat -f &#039;%Su:%Sg %Lp %N&#039; source.pass target.pass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 16.3. Задать параметры и проверить план ===&lt;br /&gt;
&lt;br /&gt;
В примере включена проверка цепочки сертификатов и имён обоих серверов, разрешён TLS 1.2 и новее. Даты доставки и флаги новых писем копируются; повторный запуск не перезаписывает флаги уже перенесённых сообщений. &amp;lt;code&amp;gt;--syncduplicates&amp;lt;/code&amp;gt; сохраняет также повторяющиеся экземпляры писем источника. Не меняйте способ сопоставления сообщений между запусками одного переноса.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
set -- \&lt;br /&gt;
  --host1 imap.yandex.ru --port1 993 --ssl1 \&lt;br /&gt;
  --user1 source@example.org --passfile1 /opt/mail/migration/yandex-source/source.pass \&lt;br /&gt;
  --sslargs1 SSL_verify_mode=1 \&lt;br /&gt;
  --sslargs1 SSL_verifycn_scheme=imap \&lt;br /&gt;
  --sslargs1 SSL_verifycn_name=imap.yandex.ru \&lt;br /&gt;
  --sslargs1 SSL_hostname=imap.yandex.ru \&lt;br /&gt;
  --sslargs1 SSL_ca_file=/etc/ssl/cert.pem \&lt;br /&gt;
  --sslargs1 SSL_version=SSLv23:!SSLv3:!SSLv2:!TLSv1:!TLSv1_1 \&lt;br /&gt;
  --host2 mail.example.net --port2 993 --ssl2 \&lt;br /&gt;
  --user2 target@example.net --passfile2 /opt/mail/migration/yandex-source/target.pass \&lt;br /&gt;
  --sslargs2 SSL_verify_mode=1 \&lt;br /&gt;
  --sslargs2 SSL_verifycn_scheme=imap \&lt;br /&gt;
  --sslargs2 SSL_verifycn_name=mail.example.net \&lt;br /&gt;
  --sslargs2 SSL_hostname=mail.example.net \&lt;br /&gt;
  --sslargs2 SSL_ca_file=/etc/ssl/cert.pem \&lt;br /&gt;
  --sslargs2 SSL_version=SSLv23:!SSLv3:!SSLv2:!TLSv1:!TLSv1_1 \&lt;br /&gt;
  --subfolder2 Yandex-source \&lt;br /&gt;
  --useheader Message-Id --syncduplicates --syncinternaldates --noresyncflags \&lt;br /&gt;
  --noexpunge1 --noexpunge2 --nouidexpunge2 \&lt;br /&gt;
  --noemailreport1 --noemailreport2 --noreleasecheck \&lt;br /&gt;
  --tmpdir /opt/mail/migration/yandex-source/cache \&lt;br /&gt;
  --logdir /opt/mail/migration/yandex-source/log \&lt;br /&gt;
  --pidfile /opt/mail/migration/yandex-source/imapsync.pid --pidfilelocking&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверки ниже не копируют письма. Первая должна подтвердить вход в оба ящика; следующая показывает соответствие папок, затем выводятся количество и объём писем. После любой ошибки сначала устраните её и повторите проверку, прежде чем продолжать.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --justlogin --logfile login.log&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --dry --justfolders --logfile folders.log&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --dry --justfoldersizes --logfile sizes.log&lt;br /&gt;
df -h /opt&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сопоставьте суммарный объём источника из &amp;lt;code&amp;gt;sizes.log&amp;lt;/code&amp;gt; со свободным местом &amp;lt;code&amp;gt;df&amp;lt;/code&amp;gt;. Для первого полного копирования оставьте запас не менее 1 ГиБ сверх объёма переносимых писем, а для большого числа маленьких писем — дополнительное место под файловые метаданные. Если места недостаточно, не начинайте копирование: сначала увеличьте раздел или уменьшите согласованный объём переноса. В выводе плана все создаваемые папки должны находиться под &amp;lt;code&amp;gt;Yandex-source&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== 16.4. Скопировать и проверить результат ===&lt;br /&gt;
&lt;br /&gt;
Во время ручного запуска держите SSH-сессию открытой. Не запускайте два переноса этой пары одновременно; файл PID дополнительно защищает от параллельного старта. Не добавляйте параметры удаления &amp;lt;code&amp;gt;--delete1&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;--delete2&amp;lt;/code&amp;gt; или удаления папок. Включённые &amp;lt;code&amp;gt;--noexpunge*&amp;lt;/code&amp;gt; запрещают очистку удалённых сообщений.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --logfile copy.log&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После успешного завершения выполните повторную проверку с теми же параметрами:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --dry --logfile verify.log&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверьте код завершения &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, отсутствие ошибок в итогах imapsync и отсутствие новых писем, предлагаемых к копированию в проверочном запуске. Если источник получил новые сообщения во время переноса, повторите обычное копирование с теми же параметрами и затем снова проверку. Откройте папку &amp;lt;code&amp;gt;Yandex-source&amp;lt;/code&amp;gt; и её подпапки в SnappyMail; выборочно сравните письма, вложения, даты и состояние прочитано/непрочитано с источником.&lt;br /&gt;
&lt;br /&gt;
Если перенос прервался, журналы и cache остаются в защищённом каталоге. После устранения причины повторите команду с прежними параметрами и cache. Перед перезапуском убедитесь, что предыдущий процесс завершён; наличие старого PID-файла само по себе не доказывает, что процесс ещё работает.&lt;br /&gt;
&lt;br /&gt;
После окончательной проверки и завершения всех процессов удалите временные файлы паролей:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
rm -f -- /opt/mail/migration/yandex-source/source.pass /opt/mail/migration/yandex-source/target.pass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Затем отзовите использованный пароль приложения Яндекса, если он был создан только для миграции. Сохранённые журналы доступны root и могут содержать адреса и названия папок; не публикуйте их без обезличивания.&lt;br /&gt;
&lt;br /&gt;
=== 16.5. Ошибки доступа ===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Invalid credentials or IMAP is disabled&amp;lt;/code&amp;gt;: проверьте полный адрес источника, пароль приложения и разрешение IMAP у пользователя и организации. Обычный пароль аккаунта может не подходить для почтового клиента.&lt;br /&gt;
* &amp;lt;code&amp;gt;To use Yandex Mail in other email clients, you need a Yandex 360 for Business plan&amp;lt;/code&amp;gt;: доступ к IMAP ограничен условиями тарифа Яндекса. Сначала восстановите доступ на стороне Яндекса; изменение параметров Dovecot эту причину не устраняет.&lt;br /&gt;
* Ошибка сертификата: исправьте имя сервера, время или цепочку сертификатов. Не отключайте &amp;lt;code&amp;gt;SSL_verify_mode&amp;lt;/code&amp;gt; и проверку имени.&lt;br /&gt;
* Недостаточно места: остановите перенос, освободите или увеличьте хранилище и повторите с прежними параметрами. Не удаляйте исходные письма ради освобождения места на назначении.&lt;br /&gt;
&lt;br /&gt;
Источники: [https://imapsync.lamiral.info/README руководство imapsync], [https://imapsync.lamiral.info/FAQ.d/FAQ.Yandex.txt параметры Яндекса], [https://imapsync.lamiral.info/FAQ.d/FAQ.Duplicates.txt дубликаты], [https://yandex.ru/support/yandex-360/business/admin/ru/mail/email-clients/ разрешение почтовых протоколов].&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=695</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=695"/>
		<updated>2026-10-30T15:24:21Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 15.5 */ Отметка прочитано при ручном переносе INBOX в Junk&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет → TCP 25 / STARTTLS → Exim&lt;br /&gt;
Клиент   → TCP 465 TLS или 587 STARTTLS + AUTH → Exim&lt;br /&gt;
Exim     → LMTP Unix socket → Dovecot → /opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim     → AUTH Unix socket → Dovecot → файловый каталог пользователей&lt;br /&gt;
Клиент   → TCP 993 TLS → Dovecot&lt;br /&gt;
SnappyMail (необязательно) → те же IMAPS и SMTP AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
sysrc hostname=mail.example.org&lt;br /&gt;
hostname mail.example.org&lt;br /&gt;
sysrc defaultrouter=198.51.100.1&lt;br /&gt;
# Только если в работающей таблице отсутствует default route:&lt;br /&gt;
route add default 198.51.100.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo /dev/da1 | awk &#039;{print $3}&#039;)&lt;br /&gt;
test -n &amp;quot;$disk_bytes&amp;quot; &amp;amp;&amp;amp; test &amp;quot;$disk_bytes&amp;quot; -gt 0 || exit 1&lt;br /&gt;
cmp -s -n &amp;quot;$disk_bytes&amp;quot; /dev/da1 /dev/zero&lt;br /&gt;
printf &#039;cmp status: %s\n&#039; &amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
pkg update&lt;br /&gt;
pkg install -n exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
# После проверки плана:&lt;br /&gt;
pkg install -y exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
sysrc exim_enable=NO dovecot_enable=NO&lt;br /&gt;
sysrc sendmail_enable=NONE sendmail_submit_enable=NO&lt;br /&gt;
sysrc sendmail_outbound_enable=NO sendmail_msp_queue_enable=NO&lt;br /&gt;
service sendmail onestop&lt;br /&gt;
/usr/local/sbin/exim -bV&lt;br /&gt;
/usr/local/sbin/dovecot --version&lt;br /&gt;
pw usershow mailnull&lt;br /&gt;
pw groupshow mail&lt;br /&gt;
pw groupshow dovecot&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only review and explicit training of IMAPSieve feedback.&lt;br /&gt;
&lt;br /&gt;
No command trains automatically. list/show are local reads. learn ID spam|ham&lt;br /&gt;
is the only command that invokes rspamc. reject ID archives without training.&lt;br /&gt;
Queue root and processed must be root-owned, non-writable by mail users;&lt;br /&gt;
pending is separately owned by vmail, mode 0700. Archived originals remain in&lt;br /&gt;
processed/ID/source; only root-owned snapshots are used for training or review.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from contextlib import contextmanager&lt;br /&gt;
from email import policy&lt;br /&gt;
from email.parser import BytesHeaderParser&lt;br /&gt;
import hashlib&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import shutil&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
&lt;br /&gt;
spec = importlib.util.spec_from_file_location(&amp;quot;feedback_collector&amp;quot;, Path(__file__).with_name(&amp;quot;feedback.py&amp;quot;))&lt;br /&gt;
collector = importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE = collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError = collector.FeedbackError&lt;br /&gt;
ID_RE = re.compile(r&amp;quot;[0-9a-f]{32}\Z&amp;quot;)&lt;br /&gt;
DEFAULT_ROOT = &amp;quot;/opt/mail/rspamd-feedback&amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE = &amp;quot;/usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
HEADERS = (&amp;quot;From&amp;quot;, &amp;quot;To&amp;quot;, &amp;quot;Date&amp;quot;, &amp;quot;Subject&amp;quot;, &amp;quot;Message-ID&amp;quot;, &amp;quot;X-Spam-Status&amp;quot;, &amp;quot;X-Spam-Score&amp;quot;, &amp;quot;X-Rspamd-Action&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def emit(value):&lt;br /&gt;
    print(json.dumps(value, ensure_ascii=True, sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def require_root():&lt;br /&gt;
    if not hasattr(os, &amp;quot;geteuid&amp;quot;) or os.geteuid() != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;this administrative command requires root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_id(event_id):&lt;br /&gt;
    if not ID_RE.fullmatch(event_id):&lt;br /&gt;
        raise FeedbackError(&amp;quot;ID must be exactly 32 lowercase hexadecimal characters&amp;quot;)&lt;br /&gt;
    return event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Directory:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Nofollow directory descriptor on FreeBSD; portable fallback for local tests.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, path):&lt;br /&gt;
        self.path = Path(path)&lt;br /&gt;
        info = self.path.lstat()&lt;br /&gt;
        if not stat.S_ISDIR(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;expected a real directory&amp;quot;)&lt;br /&gt;
        self.fd = None&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            self.fd = os.open(self.path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
    def __enter__(self):&lt;br /&gt;
        return self&lt;br /&gt;
&lt;br /&gt;
    def __exit__(self, *args):&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
    def read(self, name, limit):&lt;br /&gt;
        if &amp;quot;/&amp;quot; in name or &amp;quot;\\&amp;quot; in name or name in (&amp;quot;.&amp;quot;, &amp;quot;..&amp;quot;):&lt;br /&gt;
            raise FeedbackError(&amp;quot;invalid internal filename&amp;quot;)&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=self.fd)&lt;br /&gt;
        else:&lt;br /&gt;
            path = self.path / name&lt;br /&gt;
            if path.is_symlink():&lt;br /&gt;
                raise FeedbackError(&amp;quot;symlink is not an event file&amp;quot;)&lt;br /&gt;
            fd = os.open(path, os.O_RDONLY | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0))&lt;br /&gt;
        with os.fdopen(fd, &amp;quot;rb&amp;quot;) as source:&lt;br /&gt;
            info = os.fstat(source.fileno())&lt;br /&gt;
            if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_size &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file is not a bounded, single-link regular file&amp;quot;)&lt;br /&gt;
            result = source.read(limit + 1)&lt;br /&gt;
            if len(result) &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file exceeded the size limit&amp;quot;)&lt;br /&gt;
            return result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def read_event(directory, event_id):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    # On POSIX open the event relative to the already opened parent descriptor.&lt;br /&gt;
    # The parent path is deployment-owned, and a symlink event is rejected.&lt;br /&gt;
    with Directory(Path(directory) / event_id) as entry:&lt;br /&gt;
        metadata = json.loads(entry.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
        message = entry.read(&amp;quot;message.eml&amp;quot;, MAX_MESSAGE)&lt;br /&gt;
    if not isinstance(metadata, dict) or not isinstance(metadata.get(&amp;quot;user&amp;quot;), str):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                             metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
    if metadata.get(&amp;quot;id&amp;quot;) != event_id or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    if metadata.get(&amp;quot;bytes&amp;quot;) != len(message) or metadata.get(&amp;quot;sha256&amp;quot;) != hashlib.sha256(message).hexdigest():&lt;br /&gt;
        raise FeedbackError(&amp;quot;event content does not match its metadata&amp;quot;)&lt;br /&gt;
    return metadata, message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def write_private(path, value, *, json_value=False):&lt;br /&gt;
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0)&lt;br /&gt;
    fd = os.open(path, flags, 0o600)&lt;br /&gt;
    with os.fdopen(fd, &amp;quot;wb&amp;quot;) as output:&lt;br /&gt;
        if json_value:&lt;br /&gt;
            value = (json.dumps(value, ensure_ascii=True, sort_keys=True) + &amp;quot;\n&amp;quot;).encode()&lt;br /&gt;
        output.write(value)&lt;br /&gt;
        output.flush()&lt;br /&gt;
        os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def update_receipt(directory, receipt):&lt;br /&gt;
    temporary = directory / &amp;quot;.admin-new.json&amp;quot;&lt;br /&gt;
    write_private(temporary, receipt, json_value=True)&lt;br /&gt;
    os.replace(temporary, directory / &amp;quot;admin.json&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_root_directory(path):&lt;br /&gt;
    with Directory(path):&lt;br /&gt;
        info = Path(path).stat()&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;administrative directories must be root-owned and not group/other writable&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def admin_lock(root):&lt;br /&gt;
    check_root_directory(root)&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        processed.mkdir(mode=0o700)&lt;br /&gt;
    check_root_directory(processed)&lt;br /&gt;
    fd = os.open(root / &amp;quot;.admin.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(fd, fcntl.LOCK_EX)&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def receipt_for(directory):&lt;br /&gt;
    with Directory(directory) as entry:&lt;br /&gt;
        return json.loads(entry.read(&amp;quot;admin.json&amp;quot;, 16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def find_prior(root, sha256):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return None&lt;br /&gt;
    for entry in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
            continue&lt;br /&gt;
        try:&lt;br /&gt;
            receipt = receipt_for(entry)&lt;br /&gt;
        except (OSError, ValueError, FeedbackError):&lt;br /&gt;
            # A damaged journal must never silently allow repeated training.&lt;br /&gt;
            raise FeedbackError(&amp;quot;processed journal is incomplete; inspect it before more training&amp;quot;)&lt;br /&gt;
        if receipt.get(&amp;quot;sha256&amp;quot;) == sha256 and receipt.get(&amp;quot;status&amp;quot;) in (&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learned&amp;quot;, &amp;quot;learn_uncertain&amp;quot;):&lt;br /&gt;
            return receipt&lt;br /&gt;
    return None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def prepare_archive(root, event_id, metadata, message, status, label=None, reason=None):&lt;br /&gt;
    if shutil.disk_usage(root).free &amp;lt; collector.MIN_FREE_BYTES + len(message) + 32768:&lt;br /&gt;
        raise FeedbackError(&amp;quot;insufficient free disk space to preserve the reviewed snapshot&amp;quot;)&lt;br /&gt;
    archive = root / &amp;quot;processed&amp;quot; / event_id&lt;br /&gt;
    archive.mkdir(mode=0o700)&lt;br /&gt;
    receipt = {&amp;quot;schema&amp;quot;: 1, &amp;quot;id&amp;quot;: event_id, &amp;quot;sha256&amp;quot;: metadata[&amp;quot;sha256&amp;quot;],&lt;br /&gt;
               &amp;quot;reviewed_utc&amp;quot;: collector.utc_now(), &amp;quot;status&amp;quot;: &amp;quot;preparing&amp;quot;,&lt;br /&gt;
               &amp;quot;selected_class&amp;quot;: label, &amp;quot;reason&amp;quot;: reason}&lt;br /&gt;
    write_private(archive / &amp;quot;admin.json&amp;quot;, receipt, json_value=True)&lt;br /&gt;
    write_private(archive / &amp;quot;message.eml&amp;quot;, message)&lt;br /&gt;
    write_private(archive / &amp;quot;metadata.json&amp;quot;, json.dumps(metadata, ensure_ascii=True).encode())&lt;br /&gt;
    # Keep the complete original event. Never traverse or execute files in source/.&lt;br /&gt;
    os.rename(root / &amp;quot;pending&amp;quot; / event_id, archive / &amp;quot;source&amp;quot;)&lt;br /&gt;
    receipt[&amp;quot;status&amp;quot;] = status&lt;br /&gt;
    update_receipt(archive, receipt)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;pending&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;processed&amp;quot;)&lt;br /&gt;
    return archive, receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def run_learning(message, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    executable = Path(executable)&lt;br /&gt;
    if not executable.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be an absolute path&amp;quot;)&lt;br /&gt;
    command = [str(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be an absolute regular file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be root-owned, mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        # rspamc reads the file itself; the secret never appears in argv or logs.&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;learn_&amp;quot; + label)&lt;br /&gt;
    # No -u/-d: one shared Bayes classifier. No shell and no automatic retry.&lt;br /&gt;
    return subprocess.run(command, input=message, stdout=subprocess.DEVNULL,&lt;br /&gt;
                          stderr=subprocess.DEVNULL, timeout=timeout, check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def learn_event(root, event_id, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid training class&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        prior = find_prior(root, metadata[&amp;quot;sha256&amp;quot;])&lt;br /&gt;
        if prior:&lt;br /&gt;
            raise FeedbackError(&amp;quot;this content has a prior learned or uncertain event; inspect it and reject the duplicate&amp;quot;)&lt;br /&gt;
        archive, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;learning&amp;quot;, label)&lt;br /&gt;
        try:&lt;br /&gt;
            rc = run_learning(message, label, executable=executable, controller=controller,&lt;br /&gt;
                              password_file=password_file, timeout=timeout)&lt;br /&gt;
        except subprocess.TimeoutExpired:&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client timeout; server may have learned; do not automatically retry&amp;quot;&lt;br /&gt;
        except (OSError, FeedbackError):&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_failed&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client could not start or local configuration was invalid&amp;quot;&lt;br /&gt;
        else:&lt;br /&gt;
            receipt[&amp;quot;returncode&amp;quot;] = rc&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if rc == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            if rc:&lt;br /&gt;
                receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; inspect server before any retry&amp;quot;&lt;br /&gt;
        receipt[&amp;quot;finished_utc&amp;quot;] = collector.utc_now()&lt;br /&gt;
        update_receipt(archive, receipt)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def reject_event(root, event_id, reason=&amp;quot;review rejected&amp;quot;):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if len(reason) &amp;gt; 500:&lt;br /&gt;
        raise FeedbackError(&amp;quot;reason is too long&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        _, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;rejected&amp;quot;, reason=reason)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def list_events(root, state, limit):&lt;br /&gt;
    count = 0&lt;br /&gt;
    for name in ((&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;) if state == &amp;quot;all&amp;quot; else (state,)):&lt;br /&gt;
        directory = root / name&lt;br /&gt;
        if not directory.exists():&lt;br /&gt;
            continue&lt;br /&gt;
        with Directory(directory):&lt;br /&gt;
            for entry in directory.iterdir():&lt;br /&gt;
                if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                if count &amp;gt;= limit:&lt;br /&gt;
                    emit({&amp;quot;notice&amp;quot;: &amp;quot;limit reached&amp;quot;, &amp;quot;limit&amp;quot;: limit})&lt;br /&gt;
                    return&lt;br /&gt;
                try:&lt;br /&gt;
                    with Directory(entry) as item:&lt;br /&gt;
                        metadata = json.loads(item.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    row = {key: metadata.get(key) for key in (&amp;quot;id&amp;quot;, &amp;quot;created_utc&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;user&amp;quot;, &amp;quot;source&amp;quot;, &amp;quot;destination&amp;quot;, &amp;quot;bytes&amp;quot;)}&lt;br /&gt;
                    row[&amp;quot;state&amp;quot;] = name&lt;br /&gt;
                    if name == &amp;quot;processed&amp;quot;:&lt;br /&gt;
                        row[&amp;quot;review&amp;quot;] = receipt_for(entry)&lt;br /&gt;
                    emit(row)&lt;br /&gt;
                except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                    emit({&amp;quot;id&amp;quot;: entry.name, &amp;quot;state&amp;quot;: name, &amp;quot;error&amp;quot;: &amp;quot;invalid event; manual inspection required&amp;quot;})&lt;br /&gt;
                count += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def show_event(root, event_id, state, path_only):&lt;br /&gt;
    metadata, message = read_event(root / state, check_id(event_id))&lt;br /&gt;
    if path_only:&lt;br /&gt;
        emit({&amp;quot;id&amp;quot;: event_id, &amp;quot;message_path&amp;quot;: str(root / state / event_id / &amp;quot;message.eml&amp;quot;)})&lt;br /&gt;
        return&lt;br /&gt;
    headers = BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message), 262144)])&lt;br /&gt;
    emit({&amp;quot;metadata&amp;quot;: metadata, &amp;quot;headers&amp;quot;: {name: [str(v)[:2000] for v in headers.get_all(name, [])[:10]] for name in HEADERS}})&lt;br /&gt;
    if state == &amp;quot;processed&amp;quot;:&lt;br /&gt;
        emit({&amp;quot;review&amp;quot;: receipt_for(root / state / event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def warn_processed_storage(root):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return&lt;br /&gt;
    total = count = 0&lt;br /&gt;
    for event in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(event.name) or event.is_symlink() or not event.is_dir():&lt;br /&gt;
            continue&lt;br /&gt;
        count += 1&lt;br /&gt;
        for prefix in (event, event / &amp;quot;source&amp;quot;):&lt;br /&gt;
            if prefix.is_symlink():&lt;br /&gt;
                continue&lt;br /&gt;
            for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    info = (prefix / name).lstat()&lt;br /&gt;
                    if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                        total += info.st_size&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    pass&lt;br /&gt;
    if total &amp;gt;= collector.MAX_PENDING_BYTES or count &amp;gt;= collector.MAX_PENDING_EVENTS:&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: WARNING processed archive reached 512 MiB or 1024 events; review retention manually; nothing deleted&amp;quot;, file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=DEFAULT_ROOT)&lt;br /&gt;
    sub = parser.add_subparsers(dest=&amp;quot;command&amp;quot;, required=True)&lt;br /&gt;
    listing = sub.add_parser(&amp;quot;list&amp;quot;, help=&amp;quot;JSON lines, metadata only&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;, &amp;quot;all&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--limit&amp;quot;, type=int, default=100)&lt;br /&gt;
    showing = sub.add_parser(&amp;quot;show&amp;quot;, help=&amp;quot;selected headers only; never dumps the body&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--path&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning = sub.add_parser(&amp;quot;learn&amp;quot;, help=&amp;quot;explicitly train one reviewed event, then archive it&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;label&amp;quot;, choices=(&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;))&lt;br /&gt;
    learning.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--password-file&amp;quot;, default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
                          help=&amp;quot;root-only plaintext secret file; use --no-password only for an intentionally trusted local controller&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--no-password&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    rejecting = sub.add_parser(&amp;quot;reject&amp;quot;, help=&amp;quot;archive one event without training or deleting it&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;--reason&amp;quot;, default=&amp;quot;review rejected&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    try:&lt;br /&gt;
        require_root()&lt;br /&gt;
        root = Path(args.queue_root)&lt;br /&gt;
        if not root.is_absolute():&lt;br /&gt;
            raise FeedbackError(&amp;quot;queue root must be absolute&amp;quot;)&lt;br /&gt;
        check_root_directory(root)&lt;br /&gt;
        warn_processed_storage(root)&lt;br /&gt;
        if args.command == &amp;quot;list&amp;quot;:&lt;br /&gt;
            if not 1 &amp;lt;= args.limit &amp;lt;= 10000:&lt;br /&gt;
                raise FeedbackError(&amp;quot;limit must be between 1 and 10000&amp;quot;)&lt;br /&gt;
            list_events(root, args.state, args.limit)&lt;br /&gt;
        elif args.command == &amp;quot;show&amp;quot;:&lt;br /&gt;
            show_event(root, args.id, args.state, args.path)&lt;br /&gt;
        elif args.command == &amp;quot;reject&amp;quot;:&lt;br /&gt;
            emit(reject_event(root, args.id, args.reason))&lt;br /&gt;
        else:&lt;br /&gt;
            if not 0 &amp;lt; args.timeout &amp;lt;= 300:&lt;br /&gt;
                raise FeedbackError(&amp;quot;timeout must be between 0 and 300 seconds&amp;quot;)&lt;br /&gt;
            result = learn_event(root, args.id, args.label, executable=args.rspamc_bin,&lt;br /&gt;
                                 controller=args.controller, password_file=None if args.no_password else args.password_file,&lt;br /&gt;
                                 timeout=args.timeout)&lt;br /&gt;
            emit(result)&lt;br /&gt;
            return 0 if result[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot; else 2&lt;br /&gt;
        return 0&lt;br /&gt;
    except (FeedbackError, OSError, ValueError, TypeError):&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: operation refused or failed; inspect configuration/event locally&amp;quot;, file=sys.stderr)&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    raise SystemExit(main())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only background learning from the bounded IMAPSieve feedback queue.&lt;br /&gt;
&lt;br /&gt;
The collector remains a local spool writer. This process owns controller&lt;br /&gt;
credentials, learns only validated INBOX/Junk events, preserves every original,&lt;br /&gt;
and never retries a training operation with an uncertain result. Run foreground&lt;br /&gt;
under FreeBSD daemon(8), or use --once for one bounded batch.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import signal&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
import threading&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
_spec = importlib.util.spec_from_file_location(&amp;quot;feedback_worker_admin&amp;quot;, Path(__file__).with_name(&amp;quot;feedback-admin.py&amp;quot;))&lt;br /&gt;
admin = importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError = admin.FeedbackError&lt;br /&gt;
SHA_RE = re.compile(r&amp;quot;[0-9a-f]{64}\Z&amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS = 20000&lt;br /&gt;
UNCERTAIN = {&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learn_uncertain&amp;quot;, &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;quarantined_ordering&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def log_event(kind, **fields):&lt;br /&gt;
    # Callers pass only fixed diagnostics, numeric counts, event IDs and classes.&lt;br /&gt;
    print(json.dumps({&amp;quot;event&amp;quot;: kind, **fields}, ensure_ascii=True, sort_keys=True), flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _timestamp_ns(value):&lt;br /&gt;
    if not isinstance(value, str):&lt;br /&gt;
        raise ValueError(&amp;quot;missing timestamp&amp;quot;)&lt;br /&gt;
    parsed = datetime.fromisoformat(value.replace(&amp;quot;Z&amp;quot;, &amp;quot;+00:00&amp;quot;))&lt;br /&gt;
    if parsed.tzinfo is None:&lt;br /&gt;
        raise ValueError(&amp;quot;timestamp must have timezone&amp;quot;)&lt;br /&gt;
    return int(parsed.timestamp() * 1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def order_events(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read bounded metadata, ordered by collector created_ns, then event ID.&lt;br /&gt;
&lt;br /&gt;
    Legacy second-resolution timestamps are permitted only when the same&lt;br /&gt;
    content has no contradictory labels at an identical timestamp. Such a tie&lt;br /&gt;
    cannot be resolved from random event IDs and is quarantined without learning.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    pending = Path(root) / &amp;quot;pending&amp;quot;&lt;br /&gt;
    rows = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    with admin.Directory(pending):&lt;br /&gt;
        with os.scandir(pending) as entries:&lt;br /&gt;
            for entry in entries:&lt;br /&gt;
                if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                count += 1&lt;br /&gt;
                if count &amp;gt; admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
                    raise FeedbackError(&amp;quot;pending queue scan bound exceeded&amp;quot;)&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(pending / entry.name) as directory:&lt;br /&gt;
                        metadata = json.loads(directory.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    if not isinstance(metadata, dict):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid metadata&amp;quot;)&lt;br /&gt;
                    metadata = dict(metadata)&lt;br /&gt;
                    if metadata.get(&amp;quot;id&amp;quot;) != entry.name or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid event identity&amp;quot;)&lt;br /&gt;
                    if not isinstance(metadata.get(&amp;quot;sha256&amp;quot;), str) or not SHA_RE.fullmatch(metadata[&amp;quot;sha256&amp;quot;]):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid digest&amp;quot;)&lt;br /&gt;
                    admin.collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                                                   metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
                    timestamp = metadata.get(&amp;quot;created_ns&amp;quot;)&lt;br /&gt;
                    if isinstance(timestamp, int) and not isinstance(timestamp, bool) and timestamp &amp;gt; 0:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = timestamp&lt;br /&gt;
                    elif &amp;quot;created_ns&amp;quot; not in metadata:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = _timestamp_ns(metadata.get(&amp;quot;created_utc&amp;quot;))&lt;br /&gt;
                    else:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = 0&lt;br /&gt;
                        metadata[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
                    rows.append(metadata)&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    # A manual administrator may have archived this candidate.&lt;br /&gt;
                    continue&lt;br /&gt;
                except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                    rows.append({&amp;quot;id&amp;quot;: entry.name, &amp;quot;_invalid&amp;quot;: True, &amp;quot;_order_ns&amp;quot;: 0})&lt;br /&gt;
    tied = {}&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
            tied.setdefault((row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;]), set()).add(row[&amp;quot;label&amp;quot;])&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;) and len(tied[(row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;])]) &amp;gt; 1:&lt;br /&gt;
            row[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
    return sorted(rows, key=lambda row: (row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;id&amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _journal(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read root-owned receipts; successful classes use durable worker sequence.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    processed = Path(root) / &amp;quot;processed&amp;quot;&lt;br /&gt;
    latest, blocked = {}, set()&lt;br /&gt;
    max_sequence = used_bytes = count = 0&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
    admin.check_root_directory(processed)&lt;br /&gt;
    with os.scandir(processed) as entries:&lt;br /&gt;
        for entry in entries:&lt;br /&gt;
            if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                continue&lt;br /&gt;
            count += 1&lt;br /&gt;
            if count &amp;gt; MAX_JOURNAL_EVENTS:&lt;br /&gt;
                raise FeedbackError(&amp;quot;processed journal scan bound exceeded&amp;quot;)&lt;br /&gt;
            directory = processed / entry.name&lt;br /&gt;
            receipt = admin.receipt_for(directory)&lt;br /&gt;
            if not isinstance(receipt, dict) or receipt.get(&amp;quot;id&amp;quot;) != entry.name:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed receipt&amp;quot;)&lt;br /&gt;
            digest = receipt.get(&amp;quot;sha256&amp;quot;)&lt;br /&gt;
            if not isinstance(digest, str) or not SHA_RE.fullmatch(digest):&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed digest&amp;quot;)&lt;br /&gt;
            sequence = receipt.get(&amp;quot;worker_sequence&amp;quot;, 0)&lt;br /&gt;
            if not isinstance(sequence, int) or isinstance(sequence, bool) or sequence &amp;lt; 0:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed sequence&amp;quot;)&lt;br /&gt;
            max_sequence = max(max_sequence, sequence)&lt;br /&gt;
            status = receipt.get(&amp;quot;status&amp;quot;)&lt;br /&gt;
            if status in UNCERTAIN:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
            if status == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                label = receipt.get(&amp;quot;selected_class&amp;quot;)&lt;br /&gt;
                if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
                    raise FeedbackError(&amp;quot;invalid learned class&amp;quot;)&lt;br /&gt;
                # Existing manual receipts predate automatic processing; the old&lt;br /&gt;
                # admin refuses further training of a SHA once it has a receipt.&lt;br /&gt;
                fallback = _timestamp_ns(receipt.get(&amp;quot;finished_utc&amp;quot;, receipt.get(&amp;quot;reviewed_utc&amp;quot;)))&lt;br /&gt;
                position = (bool(sequence), sequence if sequence else fallback, entry.name)&lt;br /&gt;
                if digest not in latest or position &amp;gt; latest[digest][0]:&lt;br /&gt;
                    latest[digest] = (position, label)&lt;br /&gt;
            # Count snapshots and preserved originals, never recursively walk&lt;br /&gt;
            # arbitrary files in the mail-user-owned source/ directory.&lt;br /&gt;
            for part in (directory, directory / &amp;quot;source&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(part) as safe:&lt;br /&gt;
                        for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                            try:&lt;br /&gt;
                                if safe.fd is not None:&lt;br /&gt;
                                    info = os.stat(name, dir_fd=safe.fd, follow_symlinks=False)&lt;br /&gt;
                                else:&lt;br /&gt;
                                    info = (part / name).lstat()&lt;br /&gt;
                                if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                                    used_bytes += info.st_size&lt;br /&gt;
                            except FileNotFoundError:&lt;br /&gt;
                                continue&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    continue&lt;br /&gt;
    return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _check_trusted_parent_chain(path):&lt;br /&gt;
    if os.name != &amp;quot;posix&amp;quot;:&lt;br /&gt;
        return&lt;br /&gt;
    for parent in Path(path).parents:&lt;br /&gt;
        link_info = parent.lstat()&lt;br /&gt;
        info = parent.stat()&lt;br /&gt;
        if (link_info.st_uid != 0 or not stat.S_ISDIR(info.st_mode)&lt;br /&gt;
                or info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;executable parent directories must be controlled by root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def resolve_executable(executable):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Allow FreeBSD package symlinks, but pin both operations to their target.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    source = Path(executable)&lt;br /&gt;
    if not source.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be absolute&amp;quot;)&lt;br /&gt;
    source_info = source.lstat()&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and source_info.st_uid != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc entry must be owned by root&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(source)&lt;br /&gt;
    target = source.resolve(strict=True)&lt;br /&gt;
    info = target.lstat()&lt;br /&gt;
    if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be a regular file&amp;quot;)&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be root-owned and not writable by mail users&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(target)&lt;br /&gt;
    return str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def controller_ready(executable, controller, password_file, timeout):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Authenticated read-only controller probe; no candidate is archived yet.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    command = [resolve_executable(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;unsafe controller password file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;controller password file must be root-owned mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;stat&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        return subprocess.run(command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,&lt;br /&gt;
                              stderr=subprocess.DEVNULL, check=False, timeout=min(timeout, 5)).returncode == 0&lt;br /&gt;
    except (subprocess.TimeoutExpired, OSError):&lt;br /&gt;
        return False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def process_pending(root, *, executable=&amp;quot;/usr/local/bin/rspamc&amp;quot;, controller=&amp;quot;127.0.0.1:11334&amp;quot;,&lt;br /&gt;
                    password_file=admin.DEFAULT_PASSWORD_FILE, timeout=30, batch_size=20,&lt;br /&gt;
                    preflight=None, learner=None, stop_event=None):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Process at most batch_size candidates. Inject probes/learner for unit tests.&lt;br /&gt;
&lt;br /&gt;
    One admin lock serializes manual and background operations. A crash after a&lt;br /&gt;
    durable learning intent leaves status=learning, which blocks this SHA on the&lt;br /&gt;
    next pass; subsequent candidates are preserved as quarantined_uncertain.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    root = Path(root)&lt;br /&gt;
    preflight = controller_ready if preflight is None else preflight&lt;br /&gt;
    if learner is None:&lt;br /&gt;
        # FreeBSD pkg installs rspamc as a root-owned versioned symlink. Resolve&lt;br /&gt;
        # it once so preflight and training invoke the same trusted executable.&lt;br /&gt;
        executable = resolve_executable(executable)&lt;br /&gt;
        learner = admin.run_learning&lt;br /&gt;
    summary = {&amp;quot;learned&amp;quot;: 0, &amp;quot;duplicates&amp;quot;: 0, &amp;quot;quarantined&amp;quot;: 0, &amp;quot;invalid&amp;quot;: 0,&lt;br /&gt;
               &amp;quot;pending&amp;quot;: 0, &amp;quot;controller_unavailable&amp;quot;: False, &amp;quot;storage_blocked&amp;quot;: False,&lt;br /&gt;
               &amp;quot;archive_warning&amp;quot;: False}&lt;br /&gt;
    with admin.admin_lock(root):&lt;br /&gt;
        events = order_events(root)&lt;br /&gt;
        summary[&amp;quot;pending&amp;quot;] = len(events)&lt;br /&gt;
        if not events:&lt;br /&gt;
            return summary&lt;br /&gt;
        latest, blocked, sequence, used_bytes, archive_count = _journal(root)&lt;br /&gt;
        summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
        if used_bytes &amp;gt;= admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
            summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        if not preflight(executable, controller, password_file, timeout):&lt;br /&gt;
            summary[&amp;quot;controller_unavailable&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        handled = 0&lt;br /&gt;
        for candidate in events:&lt;br /&gt;
            if handled &amp;gt;= batch_size or (stop_event is not None and stop_event.is_set()):&lt;br /&gt;
                break&lt;br /&gt;
            if candidate.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            event_id = candidate[&amp;quot;id&amp;quot;]&lt;br /&gt;
            if (root / &amp;quot;processed&amp;quot; / event_id).exists():&lt;br /&gt;
                # Crash before prepare_archive moved pending/ID: its root-owned&lt;br /&gt;
                # intent is authoritative. Preserve the pending original for&lt;br /&gt;
                # manual recovery and continue independent content, never retry.&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            try:&lt;br /&gt;
                metadata, message = admin.read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
            except FileNotFoundError:&lt;br /&gt;
                continue&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            # Do not act if the queued ordering/identity changed since enumeration.&lt;br /&gt;
            if any(metadata.get(key) != candidate.get(key) for key in&lt;br /&gt;
                   (&amp;quot;sha256&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;created_ns&amp;quot;, &amp;quot;created_utc&amp;quot;)):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            if used_bytes + 2 * len(message) + 32768 &amp;gt; admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            label, digest = metadata[&amp;quot;label&amp;quot;], metadata[&amp;quot;sha256&amp;quot;]&lt;br /&gt;
            if candidate.get(&amp;quot;_ordering_invalid&amp;quot;):&lt;br /&gt;
                status, reason = &amp;quot;quarantined_ordering&amp;quot;, &amp;quot;event ordering is ambiguous&amp;quot;&lt;br /&gt;
            elif digest in blocked:&lt;br /&gt;
                status, reason = &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;a prior learning operation has an uncertain result&amp;quot;&lt;br /&gt;
            elif digest in latest and latest[digest][1] == label:&lt;br /&gt;
                status, reason = &amp;quot;duplicate&amp;quot;, &amp;quot;same class as the latest successful learning operation&amp;quot;&lt;br /&gt;
            else:&lt;br /&gt;
                status, reason = &amp;quot;learning&amp;quot;, &amp;quot;automatic explicit IMAP feedback&amp;quot;&lt;br /&gt;
            try:&lt;br /&gt;
                archive, receipt = admin.prepare_archive(root, event_id, metadata, message, status, label, reason)&lt;br /&gt;
            except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                # prepare_archive writes a root journal before moving the source;&lt;br /&gt;
                # stop instead of retrying an incompletely prepared operation.&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            sequence += 1&lt;br /&gt;
            receipt.update({&amp;quot;mode&amp;quot;: &amp;quot;automatic_imap_feedback&amp;quot;, &amp;quot;worker_sequence&amp;quot;: sequence,&lt;br /&gt;
                            &amp;quot;processed_ns&amp;quot;: time.time_ns(), &amp;quot;source_created_ns&amp;quot;: candidate[&amp;quot;_order_ns&amp;quot;]})&lt;br /&gt;
            if digest in latest:&lt;br /&gt;
                receipt[&amp;quot;previous_successful_class&amp;quot;] = latest[digest][1]&lt;br /&gt;
            # Durable intent exists before the only call that may change Bayes.&lt;br /&gt;
            admin.update_receipt(archive, receipt)&lt;br /&gt;
            if status == &amp;quot;learning&amp;quot;:&lt;br /&gt;
                try:&lt;br /&gt;
                    returncode = learner(message, label, executable=executable, controller=controller,&lt;br /&gt;
                                         password_file=password_file, timeout=timeout)&lt;br /&gt;
                except (subprocess.TimeoutExpired, OSError, FeedbackError):&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    receipt[&amp;quot;error&amp;quot;] = &amp;quot;client failure; server may have learned; no automatic retry&amp;quot;&lt;br /&gt;
                else:&lt;br /&gt;
                    receipt[&amp;quot;returncode&amp;quot;] = returncode&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if returncode == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    if returncode != 0:&lt;br /&gt;
                        receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; no automatic retry&amp;quot;&lt;br /&gt;
                receipt[&amp;quot;finished_utc&amp;quot;] = admin.collector.utc_now()&lt;br /&gt;
                admin.update_receipt(archive, receipt)&lt;br /&gt;
                if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                    latest[digest] = ((True, sequence, event_id), label)&lt;br /&gt;
                    summary[&amp;quot;learned&amp;quot;] += 1&lt;br /&gt;
                else:&lt;br /&gt;
                    blocked.add(digest)&lt;br /&gt;
                    summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            elif status == &amp;quot;duplicate&amp;quot;:&lt;br /&gt;
                summary[&amp;quot;duplicates&amp;quot;] += 1&lt;br /&gt;
            else:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
                summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            handled += 1&lt;br /&gt;
            used_bytes += 2 * len(message) + 32768&lt;br /&gt;
            archive_count += 1&lt;br /&gt;
            summary[&amp;quot;pending&amp;quot;] -= 1&lt;br /&gt;
            summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
            log_event(&amp;quot;feedback_processed&amp;quot;, id=event_id, label=label, status=receipt[&amp;quot;status&amp;quot;])&lt;br /&gt;
            if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learn_uncertain&amp;quot;:&lt;br /&gt;
                # The controller may have failed after the preflight. Leave all&lt;br /&gt;
                # other candidates pending until a fresh read-only probe passes.&lt;br /&gt;
                break&lt;br /&gt;
    return summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=admin.DEFAULT_ROOT)&lt;br /&gt;
    parser.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--password-file&amp;quot;, default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
    parser.add_argument(&amp;quot;--poll&amp;quot;, type=float, default=2)&lt;br /&gt;
    parser.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    parser.add_argument(&amp;quot;--batch-size&amp;quot;, type=int, default=20)&lt;br /&gt;
    parser.add_argument(&amp;quot;--once&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    admin.require_root()&lt;br /&gt;
    root = Path(args.queue_root)&lt;br /&gt;
    if not root.is_absolute() or not 0.2 &amp;lt;= args.poll &amp;lt;= 60 or not 0 &amp;lt; args.timeout &amp;lt;= 300 or not 1 &amp;lt;= args.batch_size &amp;lt;= 100:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid worker options&amp;quot;)&lt;br /&gt;
    admin.check_root_directory(root)&lt;br /&gt;
    stopping = threading.Event()&lt;br /&gt;
    for signum in (signal.SIGTERM, signal.SIGINT):&lt;br /&gt;
        signal.signal(signum, lambda *_: stopping.set())&lt;br /&gt;
    lockfd = os.open(root / &amp;quot;.worker.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)&lt;br /&gt;
        previous_state = None&lt;br /&gt;
        while not stopping.is_set():&lt;br /&gt;
            try:&lt;br /&gt;
                summary = process_pending(root, executable=args.rspamc_bin, controller=args.controller,&lt;br /&gt;
                                          password_file=args.password_file, timeout=args.timeout,&lt;br /&gt;
                                          batch_size=args.batch_size, stop_event=stopping)&lt;br /&gt;
                state = {key: value for key, value in summary.items() if key not in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)}&lt;br /&gt;
                if state != previous_state or any(summary[key] for key in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)):&lt;br /&gt;
                    log_event(&amp;quot;worker_state&amp;quot;, **summary)&lt;br /&gt;
                previous_state = state&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 2 if summary[&amp;quot;controller_unavailable&amp;quot;] or summary[&amp;quot;storage_blocked&amp;quot;] else 0&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                if previous_state != &amp;quot;local_error&amp;quot;:&lt;br /&gt;
                    log_event(&amp;quot;worker_error&amp;quot;, reason=&amp;quot;local queue, journal or configuration failure; no automatic retry of uncertain training&amp;quot;)&lt;br /&gt;
                previous_state = &amp;quot;local_error&amp;quot;&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 1&lt;br /&gt;
            stopping.wait(args.poll)&lt;br /&gt;
        return 0&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    try:&lt;br /&gt;
        raise SystemExit(main())&lt;br /&gt;
    except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
        log_event(&amp;quot;worker_start_failed&amp;quot;, reason=&amp;quot;root, configuration or worker lock required&amp;quot;)&lt;br /&gt;
        raise SystemExit(1)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При ручном перемещении INBOX → Junk письмо автоматически становится прочитанным; прочие флаги и автоматическое обучение сохраняются. Чтобы повторить настройку, дополнить список расширений require в report-spam.sieve значением &amp;lt;code&amp;gt;&amp;quot;imap4flags&amp;quot;&amp;lt;/code&amp;gt;. Сразу после первой проверки события (после блока &amp;lt;code&amp;gt;if not allof&amp;lt;/code&amp;gt; со &amp;lt;code&amp;gt;stop;&amp;lt;/code&amp;gt;), до чтения имени пользователя и вызова collector, добавить строку &amp;lt;code&amp;gt;addflag &amp;quot;\\Seen&amp;quot;;&amp;lt;/code&amp;gt;. Это правило ограничено прежним направлением INBOX → Junk. При возврате Junk → INBOX флаг прочтения не сбрасывается. После изменения повторно скомпилировать report-spam.sieve командой ниже. [https://doc.dovecot.org/main/howto/sieve.html Расширение imap4flags в Dovecot].&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Новые письма с локальной пометкой спама автоматически доставляются в Junk прочитанными; обычная почта во Входящих получает прежние флаги. Чтобы повторить настройку, после сохранения приведённого выше spam-delivery.sieve добавить &amp;lt;code&amp;gt;&amp;quot;imap4flags&amp;quot;&amp;lt;/code&amp;gt; в список расширений require. Внутри условия &amp;lt;code&amp;gt;X-Spam-Flag: YES&amp;lt;/code&amp;gt;, непосредственно перед &amp;lt;code&amp;gt;fileinto :create &amp;quot;Junk&amp;quot;;&amp;lt;/code&amp;gt;, добавить строку &amp;lt;code&amp;gt;addflag &amp;quot;\\Seen&amp;quot;;&amp;lt;/code&amp;gt;. Действие addflag добавляет флаг прочтения и сохраняет остальные флаги письма. После изменения повторно скомпилировать spam-delivery.sieve командой ниже. Уже находящиеся в папках письма не изменяются. SMTP-отклонение спама не включается; автоматическая доставка не запускает обучение. [https://www.rfc-editor.org/rfc/rfc5232 Расширение Sieve imap4flags].&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== 16. Перенос почты с Яндекса через imapsync ==&lt;br /&gt;
&lt;br /&gt;
Однократное копирование переносит письма из существующего ящика Яндекса в существующий ящик Dovecot. Пример ниже использует &amp;lt;code&amp;gt;source@example.org&amp;lt;/code&amp;gt; как источник и &amp;lt;code&amp;gt;target@example.net&amp;lt;/code&amp;gt; на &amp;lt;code&amp;gt;mail.example.net&amp;lt;/code&amp;gt; как назначение. Замените эти значения своими. Переключать MX обратно на Яндекс для такого переноса не требуется.&lt;br /&gt;
&lt;br /&gt;
Папки источника размещаются внутри отдельной папки &amp;lt;code&amp;gt;Yandex-source&amp;lt;/code&amp;gt;. Оригиналы на Яндексе и текущая почта назначения сохраняются. Команды выполняются последовательно в одной интерактивной root-сессии SSH; после переподключения параметры &amp;lt;code&amp;gt;set --&amp;lt;/code&amp;gt; нужно задать заново.&lt;br /&gt;
&lt;br /&gt;
=== 16.1. Доступ и пакеты ===&lt;br /&gt;
&lt;br /&gt;
Разрешите IMAP в настройках почтовых программ Яндекса; для организации Яндекс 360 может потребоваться также разрешение протокола в кабинете администратора. Для источника используйте пароль приложения с доступом к Почте, для назначения — пароль своего ящика Dovecot. Если Яндекс блокирует IMAP и разрешить его нельзя, imapsync не сможет прочитать письма.&lt;br /&gt;
&lt;br /&gt;
Проверьте, что &amp;lt;code&amp;gt;/opt&amp;lt;/code&amp;gt; смонтирован как UFS, целевой ящик уже существует и имя &amp;lt;code&amp;gt;mail.example.net&amp;lt;/code&amp;gt; соответствует действительному сертификату почтового сервера. Импорт выполняется в обычное хранилище IMAP; файловый каталог пользователей Exim/Dovecot менять не требуется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
sudo pkg install imapsync&lt;br /&gt;
sudo /bin/sh&lt;br /&gt;
umask 077&lt;br /&gt;
install -d -o root -g wheel -m 0700 /opt/mail/migration/yandex-source&lt;br /&gt;
install -d -o root -g wheel -m 0700 /opt/mail/migration/yandex-source/cache /opt/mail/migration/yandex-source/log&lt;br /&gt;
cd /opt/mail/migration/yandex-source&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 16.2. Ввести пароли без отображения ===&lt;br /&gt;
&lt;br /&gt;
Следующие команды запрашивают пароли в терминале и записывают их в новые файлы. Пароли не попадают в аргументы процессов, переменные окружения или историю shell. С установленной выше маской файлы имеют права &amp;lt;code&amp;gt;0600&amp;lt;/code&amp;gt;, каталог — &amp;lt;code&amp;gt;0700&amp;lt;/code&amp;gt;. Режим создания &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; отказывается перезаписывать существующие файлы; не запускайте команды повторно, если пароли уже введены.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
python3.12 -c &#039;import getpass, os, sys; assert os.geteuid() == 0 and sys.stdin.isatty(); p = getpass.getpass(&amp;quot;Пароль приложения Яндекса: &amp;quot;); assert p and not any(c in p for c in &amp;quot;\x00\r\n&amp;quot;); f = open(&amp;quot;source.pass&amp;quot;, &amp;quot;x&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;); f.write(p + &amp;quot;\n&amp;quot;); f.close()&#039;&lt;br /&gt;
python3.12 -c &#039;import getpass, os, sys; assert os.geteuid() == 0 and sys.stdin.isatty(); p = getpass.getpass(&amp;quot;Пароль целевого ящика: &amp;quot;); assert p and not any(c in p for c in &amp;quot;\x00\r\n&amp;quot;); f = open(&amp;quot;target.pass&amp;quot;, &amp;quot;x&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;); f.write(p + &amp;quot;\n&amp;quot;); f.close()&#039;&lt;br /&gt;
stat -f &#039;%Su:%Sg %Lp %N&#039; source.pass target.pass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 16.3. Задать параметры и проверить план ===&lt;br /&gt;
&lt;br /&gt;
В примере включена проверка цепочки сертификатов и имён обоих серверов, разрешён TLS 1.2 и новее. Даты доставки и флаги новых писем копируются; повторный запуск не перезаписывает флаги уже перенесённых сообщений. &amp;lt;code&amp;gt;--syncduplicates&amp;lt;/code&amp;gt; сохраняет также повторяющиеся экземпляры писем источника. Не меняйте способ сопоставления сообщений между запусками одного переноса.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
set -- \&lt;br /&gt;
  --host1 imap.yandex.ru --port1 993 --ssl1 \&lt;br /&gt;
  --user1 source@example.org --passfile1 /opt/mail/migration/yandex-source/source.pass \&lt;br /&gt;
  --sslargs1 SSL_verify_mode=1 \&lt;br /&gt;
  --sslargs1 SSL_verifycn_scheme=imap \&lt;br /&gt;
  --sslargs1 SSL_verifycn_name=imap.yandex.ru \&lt;br /&gt;
  --sslargs1 SSL_hostname=imap.yandex.ru \&lt;br /&gt;
  --sslargs1 SSL_ca_file=/etc/ssl/cert.pem \&lt;br /&gt;
  --sslargs1 SSL_version=SSLv23:!SSLv3:!SSLv2:!TLSv1:!TLSv1_1 \&lt;br /&gt;
  --host2 mail.example.net --port2 993 --ssl2 \&lt;br /&gt;
  --user2 target@example.net --passfile2 /opt/mail/migration/yandex-source/target.pass \&lt;br /&gt;
  --sslargs2 SSL_verify_mode=1 \&lt;br /&gt;
  --sslargs2 SSL_verifycn_scheme=imap \&lt;br /&gt;
  --sslargs2 SSL_verifycn_name=mail.example.net \&lt;br /&gt;
  --sslargs2 SSL_hostname=mail.example.net \&lt;br /&gt;
  --sslargs2 SSL_ca_file=/etc/ssl/cert.pem \&lt;br /&gt;
  --sslargs2 SSL_version=SSLv23:!SSLv3:!SSLv2:!TLSv1:!TLSv1_1 \&lt;br /&gt;
  --subfolder2 Yandex-source \&lt;br /&gt;
  --useheader Message-Id --syncduplicates --syncinternaldates --noresyncflags \&lt;br /&gt;
  --noexpunge1 --noexpunge2 --nouidexpunge2 \&lt;br /&gt;
  --noemailreport1 --noemailreport2 --noreleasecheck \&lt;br /&gt;
  --tmpdir /opt/mail/migration/yandex-source/cache \&lt;br /&gt;
  --logdir /opt/mail/migration/yandex-source/log \&lt;br /&gt;
  --pidfile /opt/mail/migration/yandex-source/imapsync.pid --pidfilelocking&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверки ниже не копируют письма. Первая должна подтвердить вход в оба ящика; следующая показывает соответствие папок, затем выводятся количество и объём писем. После любой ошибки сначала устраните её и повторите проверку, прежде чем продолжать.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --justlogin --logfile login.log&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --dry --justfolders --logfile folders.log&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --dry --justfoldersizes --logfile sizes.log&lt;br /&gt;
df -h /opt&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сопоставьте суммарный объём источника из &amp;lt;code&amp;gt;sizes.log&amp;lt;/code&amp;gt; со свободным местом &amp;lt;code&amp;gt;df&amp;lt;/code&amp;gt;. Для первого полного копирования оставьте запас не менее 1 ГиБ сверх объёма переносимых писем, а для большого числа маленьких писем — дополнительное место под файловые метаданные. Если места недостаточно, не начинайте копирование: сначала увеличьте раздел или уменьшите согласованный объём переноса. В выводе плана все создаваемые папки должны находиться под &amp;lt;code&amp;gt;Yandex-source&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== 16.4. Скопировать и проверить результат ===&lt;br /&gt;
&lt;br /&gt;
Во время ручного запуска держите SSH-сессию открытой. Не запускайте два переноса этой пары одновременно; файл PID дополнительно защищает от параллельного старта. Не добавляйте параметры удаления &amp;lt;code&amp;gt;--delete1&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;--delete2&amp;lt;/code&amp;gt; или удаления папок. Включённые &amp;lt;code&amp;gt;--noexpunge*&amp;lt;/code&amp;gt; запрещают очистку удалённых сообщений.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --logfile copy.log&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После успешного завершения выполните повторную проверку с теми же параметрами:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --dry --logfile verify.log&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверьте код завершения &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, отсутствие ошибок в итогах imapsync и отсутствие новых писем, предлагаемых к копированию в проверочном запуске. Если источник получил новые сообщения во время переноса, повторите обычное копирование с теми же параметрами и затем снова проверку. Откройте папку &amp;lt;code&amp;gt;Yandex-source&amp;lt;/code&amp;gt; и её подпапки в SnappyMail; выборочно сравните письма, вложения, даты и состояние прочитано/непрочитано с источником.&lt;br /&gt;
&lt;br /&gt;
Если перенос прервался, журналы и cache остаются в защищённом каталоге. После устранения причины повторите команду с прежними параметрами и cache. Перед перезапуском убедитесь, что предыдущий процесс завершён; наличие старого PID-файла само по себе не доказывает, что процесс ещё работает.&lt;br /&gt;
&lt;br /&gt;
После окончательной проверки и завершения всех процессов удалите временные файлы паролей:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
rm -f -- /opt/mail/migration/yandex-source/source.pass /opt/mail/migration/yandex-source/target.pass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Затем отзовите использованный пароль приложения Яндекса, если он был создан только для миграции. Сохранённые журналы доступны root и могут содержать адреса и названия папок; не публикуйте их без обезличивания.&lt;br /&gt;
&lt;br /&gt;
=== 16.5. Ошибки доступа ===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Invalid credentials or IMAP is disabled&amp;lt;/code&amp;gt;: проверьте полный адрес источника, пароль приложения и разрешение IMAP у пользователя и организации. Обычный пароль аккаунта может не подходить для почтового клиента.&lt;br /&gt;
* &amp;lt;code&amp;gt;To use Yandex Mail in other email clients, you need a Yandex 360 for Business plan&amp;lt;/code&amp;gt;: доступ к IMAP ограничен условиями тарифа Яндекса. Сначала восстановите доступ на стороне Яндекса; изменение параметров Dovecot эту причину не устраняет.&lt;br /&gt;
* Ошибка сертификата: исправьте имя сервера, время или цепочку сертификатов. Не отключайте &amp;lt;code&amp;gt;SSL_verify_mode&amp;lt;/code&amp;gt; и проверку имени.&lt;br /&gt;
* Недостаточно места: остановите перенос, освободите или увеличьте хранилище и повторите с прежними параметрами. Не удаляйте исходные письма ради освобождения места на назначении.&lt;br /&gt;
&lt;br /&gt;
Источники: [https://imapsync.lamiral.info/README руководство imapsync], [https://imapsync.lamiral.info/FAQ.d/FAQ.Yandex.txt параметры Яндекса], [https://imapsync.lamiral.info/FAQ.d/FAQ.Duplicates.txt дубликаты], [https://yandex.ru/support/yandex-360/business/admin/ru/mail/email-clients/ разрешение почтовых протоколов].&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=694</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=694"/>
		<updated>2026-10-30T15:23:53Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 15.7 */ Автоматическая отметка прочитано для спама при LMTP-доставке&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет → TCP 25 / STARTTLS → Exim&lt;br /&gt;
Клиент   → TCP 465 TLS или 587 STARTTLS + AUTH → Exim&lt;br /&gt;
Exim     → LMTP Unix socket → Dovecot → /opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim     → AUTH Unix socket → Dovecot → файловый каталог пользователей&lt;br /&gt;
Клиент   → TCP 993 TLS → Dovecot&lt;br /&gt;
SnappyMail (необязательно) → те же IMAPS и SMTP AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
sysrc hostname=mail.example.org&lt;br /&gt;
hostname mail.example.org&lt;br /&gt;
sysrc defaultrouter=198.51.100.1&lt;br /&gt;
# Только если в работающей таблице отсутствует default route:&lt;br /&gt;
route add default 198.51.100.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo /dev/da1 | awk &#039;{print $3}&#039;)&lt;br /&gt;
test -n &amp;quot;$disk_bytes&amp;quot; &amp;amp;&amp;amp; test &amp;quot;$disk_bytes&amp;quot; -gt 0 || exit 1&lt;br /&gt;
cmp -s -n &amp;quot;$disk_bytes&amp;quot; /dev/da1 /dev/zero&lt;br /&gt;
printf &#039;cmp status: %s\n&#039; &amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
pkg update&lt;br /&gt;
pkg install -n exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
# После проверки плана:&lt;br /&gt;
pkg install -y exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
sysrc exim_enable=NO dovecot_enable=NO&lt;br /&gt;
sysrc sendmail_enable=NONE sendmail_submit_enable=NO&lt;br /&gt;
sysrc sendmail_outbound_enable=NO sendmail_msp_queue_enable=NO&lt;br /&gt;
service sendmail onestop&lt;br /&gt;
/usr/local/sbin/exim -bV&lt;br /&gt;
/usr/local/sbin/dovecot --version&lt;br /&gt;
pw usershow mailnull&lt;br /&gt;
pw groupshow mail&lt;br /&gt;
pw groupshow dovecot&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only review and explicit training of IMAPSieve feedback.&lt;br /&gt;
&lt;br /&gt;
No command trains automatically. list/show are local reads. learn ID spam|ham&lt;br /&gt;
is the only command that invokes rspamc. reject ID archives without training.&lt;br /&gt;
Queue root and processed must be root-owned, non-writable by mail users;&lt;br /&gt;
pending is separately owned by vmail, mode 0700. Archived originals remain in&lt;br /&gt;
processed/ID/source; only root-owned snapshots are used for training or review.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from contextlib import contextmanager&lt;br /&gt;
from email import policy&lt;br /&gt;
from email.parser import BytesHeaderParser&lt;br /&gt;
import hashlib&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import shutil&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
&lt;br /&gt;
spec = importlib.util.spec_from_file_location(&amp;quot;feedback_collector&amp;quot;, Path(__file__).with_name(&amp;quot;feedback.py&amp;quot;))&lt;br /&gt;
collector = importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE = collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError = collector.FeedbackError&lt;br /&gt;
ID_RE = re.compile(r&amp;quot;[0-9a-f]{32}\Z&amp;quot;)&lt;br /&gt;
DEFAULT_ROOT = &amp;quot;/opt/mail/rspamd-feedback&amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE = &amp;quot;/usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
HEADERS = (&amp;quot;From&amp;quot;, &amp;quot;To&amp;quot;, &amp;quot;Date&amp;quot;, &amp;quot;Subject&amp;quot;, &amp;quot;Message-ID&amp;quot;, &amp;quot;X-Spam-Status&amp;quot;, &amp;quot;X-Spam-Score&amp;quot;, &amp;quot;X-Rspamd-Action&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def emit(value):&lt;br /&gt;
    print(json.dumps(value, ensure_ascii=True, sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def require_root():&lt;br /&gt;
    if not hasattr(os, &amp;quot;geteuid&amp;quot;) or os.geteuid() != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;this administrative command requires root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_id(event_id):&lt;br /&gt;
    if not ID_RE.fullmatch(event_id):&lt;br /&gt;
        raise FeedbackError(&amp;quot;ID must be exactly 32 lowercase hexadecimal characters&amp;quot;)&lt;br /&gt;
    return event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Directory:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Nofollow directory descriptor on FreeBSD; portable fallback for local tests.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, path):&lt;br /&gt;
        self.path = Path(path)&lt;br /&gt;
        info = self.path.lstat()&lt;br /&gt;
        if not stat.S_ISDIR(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;expected a real directory&amp;quot;)&lt;br /&gt;
        self.fd = None&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            self.fd = os.open(self.path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
    def __enter__(self):&lt;br /&gt;
        return self&lt;br /&gt;
&lt;br /&gt;
    def __exit__(self, *args):&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
    def read(self, name, limit):&lt;br /&gt;
        if &amp;quot;/&amp;quot; in name or &amp;quot;\\&amp;quot; in name or name in (&amp;quot;.&amp;quot;, &amp;quot;..&amp;quot;):&lt;br /&gt;
            raise FeedbackError(&amp;quot;invalid internal filename&amp;quot;)&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=self.fd)&lt;br /&gt;
        else:&lt;br /&gt;
            path = self.path / name&lt;br /&gt;
            if path.is_symlink():&lt;br /&gt;
                raise FeedbackError(&amp;quot;symlink is not an event file&amp;quot;)&lt;br /&gt;
            fd = os.open(path, os.O_RDONLY | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0))&lt;br /&gt;
        with os.fdopen(fd, &amp;quot;rb&amp;quot;) as source:&lt;br /&gt;
            info = os.fstat(source.fileno())&lt;br /&gt;
            if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_size &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file is not a bounded, single-link regular file&amp;quot;)&lt;br /&gt;
            result = source.read(limit + 1)&lt;br /&gt;
            if len(result) &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file exceeded the size limit&amp;quot;)&lt;br /&gt;
            return result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def read_event(directory, event_id):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    # On POSIX open the event relative to the already opened parent descriptor.&lt;br /&gt;
    # The parent path is deployment-owned, and a symlink event is rejected.&lt;br /&gt;
    with Directory(Path(directory) / event_id) as entry:&lt;br /&gt;
        metadata = json.loads(entry.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
        message = entry.read(&amp;quot;message.eml&amp;quot;, MAX_MESSAGE)&lt;br /&gt;
    if not isinstance(metadata, dict) or not isinstance(metadata.get(&amp;quot;user&amp;quot;), str):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                             metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
    if metadata.get(&amp;quot;id&amp;quot;) != event_id or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    if metadata.get(&amp;quot;bytes&amp;quot;) != len(message) or metadata.get(&amp;quot;sha256&amp;quot;) != hashlib.sha256(message).hexdigest():&lt;br /&gt;
        raise FeedbackError(&amp;quot;event content does not match its metadata&amp;quot;)&lt;br /&gt;
    return metadata, message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def write_private(path, value, *, json_value=False):&lt;br /&gt;
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0)&lt;br /&gt;
    fd = os.open(path, flags, 0o600)&lt;br /&gt;
    with os.fdopen(fd, &amp;quot;wb&amp;quot;) as output:&lt;br /&gt;
        if json_value:&lt;br /&gt;
            value = (json.dumps(value, ensure_ascii=True, sort_keys=True) + &amp;quot;\n&amp;quot;).encode()&lt;br /&gt;
        output.write(value)&lt;br /&gt;
        output.flush()&lt;br /&gt;
        os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def update_receipt(directory, receipt):&lt;br /&gt;
    temporary = directory / &amp;quot;.admin-new.json&amp;quot;&lt;br /&gt;
    write_private(temporary, receipt, json_value=True)&lt;br /&gt;
    os.replace(temporary, directory / &amp;quot;admin.json&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_root_directory(path):&lt;br /&gt;
    with Directory(path):&lt;br /&gt;
        info = Path(path).stat()&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;administrative directories must be root-owned and not group/other writable&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def admin_lock(root):&lt;br /&gt;
    check_root_directory(root)&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        processed.mkdir(mode=0o700)&lt;br /&gt;
    check_root_directory(processed)&lt;br /&gt;
    fd = os.open(root / &amp;quot;.admin.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(fd, fcntl.LOCK_EX)&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def receipt_for(directory):&lt;br /&gt;
    with Directory(directory) as entry:&lt;br /&gt;
        return json.loads(entry.read(&amp;quot;admin.json&amp;quot;, 16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def find_prior(root, sha256):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return None&lt;br /&gt;
    for entry in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
            continue&lt;br /&gt;
        try:&lt;br /&gt;
            receipt = receipt_for(entry)&lt;br /&gt;
        except (OSError, ValueError, FeedbackError):&lt;br /&gt;
            # A damaged journal must never silently allow repeated training.&lt;br /&gt;
            raise FeedbackError(&amp;quot;processed journal is incomplete; inspect it before more training&amp;quot;)&lt;br /&gt;
        if receipt.get(&amp;quot;sha256&amp;quot;) == sha256 and receipt.get(&amp;quot;status&amp;quot;) in (&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learned&amp;quot;, &amp;quot;learn_uncertain&amp;quot;):&lt;br /&gt;
            return receipt&lt;br /&gt;
    return None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def prepare_archive(root, event_id, metadata, message, status, label=None, reason=None):&lt;br /&gt;
    if shutil.disk_usage(root).free &amp;lt; collector.MIN_FREE_BYTES + len(message) + 32768:&lt;br /&gt;
        raise FeedbackError(&amp;quot;insufficient free disk space to preserve the reviewed snapshot&amp;quot;)&lt;br /&gt;
    archive = root / &amp;quot;processed&amp;quot; / event_id&lt;br /&gt;
    archive.mkdir(mode=0o700)&lt;br /&gt;
    receipt = {&amp;quot;schema&amp;quot;: 1, &amp;quot;id&amp;quot;: event_id, &amp;quot;sha256&amp;quot;: metadata[&amp;quot;sha256&amp;quot;],&lt;br /&gt;
               &amp;quot;reviewed_utc&amp;quot;: collector.utc_now(), &amp;quot;status&amp;quot;: &amp;quot;preparing&amp;quot;,&lt;br /&gt;
               &amp;quot;selected_class&amp;quot;: label, &amp;quot;reason&amp;quot;: reason}&lt;br /&gt;
    write_private(archive / &amp;quot;admin.json&amp;quot;, receipt, json_value=True)&lt;br /&gt;
    write_private(archive / &amp;quot;message.eml&amp;quot;, message)&lt;br /&gt;
    write_private(archive / &amp;quot;metadata.json&amp;quot;, json.dumps(metadata, ensure_ascii=True).encode())&lt;br /&gt;
    # Keep the complete original event. Never traverse or execute files in source/.&lt;br /&gt;
    os.rename(root / &amp;quot;pending&amp;quot; / event_id, archive / &amp;quot;source&amp;quot;)&lt;br /&gt;
    receipt[&amp;quot;status&amp;quot;] = status&lt;br /&gt;
    update_receipt(archive, receipt)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;pending&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;processed&amp;quot;)&lt;br /&gt;
    return archive, receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def run_learning(message, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    executable = Path(executable)&lt;br /&gt;
    if not executable.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be an absolute path&amp;quot;)&lt;br /&gt;
    command = [str(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be an absolute regular file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be root-owned, mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        # rspamc reads the file itself; the secret never appears in argv or logs.&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;learn_&amp;quot; + label)&lt;br /&gt;
    # No -u/-d: one shared Bayes classifier. No shell and no automatic retry.&lt;br /&gt;
    return subprocess.run(command, input=message, stdout=subprocess.DEVNULL,&lt;br /&gt;
                          stderr=subprocess.DEVNULL, timeout=timeout, check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def learn_event(root, event_id, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid training class&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        prior = find_prior(root, metadata[&amp;quot;sha256&amp;quot;])&lt;br /&gt;
        if prior:&lt;br /&gt;
            raise FeedbackError(&amp;quot;this content has a prior learned or uncertain event; inspect it and reject the duplicate&amp;quot;)&lt;br /&gt;
        archive, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;learning&amp;quot;, label)&lt;br /&gt;
        try:&lt;br /&gt;
            rc = run_learning(message, label, executable=executable, controller=controller,&lt;br /&gt;
                              password_file=password_file, timeout=timeout)&lt;br /&gt;
        except subprocess.TimeoutExpired:&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client timeout; server may have learned; do not automatically retry&amp;quot;&lt;br /&gt;
        except (OSError, FeedbackError):&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_failed&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client could not start or local configuration was invalid&amp;quot;&lt;br /&gt;
        else:&lt;br /&gt;
            receipt[&amp;quot;returncode&amp;quot;] = rc&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if rc == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            if rc:&lt;br /&gt;
                receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; inspect server before any retry&amp;quot;&lt;br /&gt;
        receipt[&amp;quot;finished_utc&amp;quot;] = collector.utc_now()&lt;br /&gt;
        update_receipt(archive, receipt)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def reject_event(root, event_id, reason=&amp;quot;review rejected&amp;quot;):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if len(reason) &amp;gt; 500:&lt;br /&gt;
        raise FeedbackError(&amp;quot;reason is too long&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        _, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;rejected&amp;quot;, reason=reason)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def list_events(root, state, limit):&lt;br /&gt;
    count = 0&lt;br /&gt;
    for name in ((&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;) if state == &amp;quot;all&amp;quot; else (state,)):&lt;br /&gt;
        directory = root / name&lt;br /&gt;
        if not directory.exists():&lt;br /&gt;
            continue&lt;br /&gt;
        with Directory(directory):&lt;br /&gt;
            for entry in directory.iterdir():&lt;br /&gt;
                if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                if count &amp;gt;= limit:&lt;br /&gt;
                    emit({&amp;quot;notice&amp;quot;: &amp;quot;limit reached&amp;quot;, &amp;quot;limit&amp;quot;: limit})&lt;br /&gt;
                    return&lt;br /&gt;
                try:&lt;br /&gt;
                    with Directory(entry) as item:&lt;br /&gt;
                        metadata = json.loads(item.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    row = {key: metadata.get(key) for key in (&amp;quot;id&amp;quot;, &amp;quot;created_utc&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;user&amp;quot;, &amp;quot;source&amp;quot;, &amp;quot;destination&amp;quot;, &amp;quot;bytes&amp;quot;)}&lt;br /&gt;
                    row[&amp;quot;state&amp;quot;] = name&lt;br /&gt;
                    if name == &amp;quot;processed&amp;quot;:&lt;br /&gt;
                        row[&amp;quot;review&amp;quot;] = receipt_for(entry)&lt;br /&gt;
                    emit(row)&lt;br /&gt;
                except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                    emit({&amp;quot;id&amp;quot;: entry.name, &amp;quot;state&amp;quot;: name, &amp;quot;error&amp;quot;: &amp;quot;invalid event; manual inspection required&amp;quot;})&lt;br /&gt;
                count += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def show_event(root, event_id, state, path_only):&lt;br /&gt;
    metadata, message = read_event(root / state, check_id(event_id))&lt;br /&gt;
    if path_only:&lt;br /&gt;
        emit({&amp;quot;id&amp;quot;: event_id, &amp;quot;message_path&amp;quot;: str(root / state / event_id / &amp;quot;message.eml&amp;quot;)})&lt;br /&gt;
        return&lt;br /&gt;
    headers = BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message), 262144)])&lt;br /&gt;
    emit({&amp;quot;metadata&amp;quot;: metadata, &amp;quot;headers&amp;quot;: {name: [str(v)[:2000] for v in headers.get_all(name, [])[:10]] for name in HEADERS}})&lt;br /&gt;
    if state == &amp;quot;processed&amp;quot;:&lt;br /&gt;
        emit({&amp;quot;review&amp;quot;: receipt_for(root / state / event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def warn_processed_storage(root):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return&lt;br /&gt;
    total = count = 0&lt;br /&gt;
    for event in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(event.name) or event.is_symlink() or not event.is_dir():&lt;br /&gt;
            continue&lt;br /&gt;
        count += 1&lt;br /&gt;
        for prefix in (event, event / &amp;quot;source&amp;quot;):&lt;br /&gt;
            if prefix.is_symlink():&lt;br /&gt;
                continue&lt;br /&gt;
            for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    info = (prefix / name).lstat()&lt;br /&gt;
                    if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                        total += info.st_size&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    pass&lt;br /&gt;
    if total &amp;gt;= collector.MAX_PENDING_BYTES or count &amp;gt;= collector.MAX_PENDING_EVENTS:&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: WARNING processed archive reached 512 MiB or 1024 events; review retention manually; nothing deleted&amp;quot;, file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=DEFAULT_ROOT)&lt;br /&gt;
    sub = parser.add_subparsers(dest=&amp;quot;command&amp;quot;, required=True)&lt;br /&gt;
    listing = sub.add_parser(&amp;quot;list&amp;quot;, help=&amp;quot;JSON lines, metadata only&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;, &amp;quot;all&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--limit&amp;quot;, type=int, default=100)&lt;br /&gt;
    showing = sub.add_parser(&amp;quot;show&amp;quot;, help=&amp;quot;selected headers only; never dumps the body&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--path&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning = sub.add_parser(&amp;quot;learn&amp;quot;, help=&amp;quot;explicitly train one reviewed event, then archive it&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;label&amp;quot;, choices=(&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;))&lt;br /&gt;
    learning.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--password-file&amp;quot;, default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
                          help=&amp;quot;root-only plaintext secret file; use --no-password only for an intentionally trusted local controller&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--no-password&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    rejecting = sub.add_parser(&amp;quot;reject&amp;quot;, help=&amp;quot;archive one event without training or deleting it&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;--reason&amp;quot;, default=&amp;quot;review rejected&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    try:&lt;br /&gt;
        require_root()&lt;br /&gt;
        root = Path(args.queue_root)&lt;br /&gt;
        if not root.is_absolute():&lt;br /&gt;
            raise FeedbackError(&amp;quot;queue root must be absolute&amp;quot;)&lt;br /&gt;
        check_root_directory(root)&lt;br /&gt;
        warn_processed_storage(root)&lt;br /&gt;
        if args.command == &amp;quot;list&amp;quot;:&lt;br /&gt;
            if not 1 &amp;lt;= args.limit &amp;lt;= 10000:&lt;br /&gt;
                raise FeedbackError(&amp;quot;limit must be between 1 and 10000&amp;quot;)&lt;br /&gt;
            list_events(root, args.state, args.limit)&lt;br /&gt;
        elif args.command == &amp;quot;show&amp;quot;:&lt;br /&gt;
            show_event(root, args.id, args.state, args.path)&lt;br /&gt;
        elif args.command == &amp;quot;reject&amp;quot;:&lt;br /&gt;
            emit(reject_event(root, args.id, args.reason))&lt;br /&gt;
        else:&lt;br /&gt;
            if not 0 &amp;lt; args.timeout &amp;lt;= 300:&lt;br /&gt;
                raise FeedbackError(&amp;quot;timeout must be between 0 and 300 seconds&amp;quot;)&lt;br /&gt;
            result = learn_event(root, args.id, args.label, executable=args.rspamc_bin,&lt;br /&gt;
                                 controller=args.controller, password_file=None if args.no_password else args.password_file,&lt;br /&gt;
                                 timeout=args.timeout)&lt;br /&gt;
            emit(result)&lt;br /&gt;
            return 0 if result[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot; else 2&lt;br /&gt;
        return 0&lt;br /&gt;
    except (FeedbackError, OSError, ValueError, TypeError):&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: operation refused or failed; inspect configuration/event locally&amp;quot;, file=sys.stderr)&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    raise SystemExit(main())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only background learning from the bounded IMAPSieve feedback queue.&lt;br /&gt;
&lt;br /&gt;
The collector remains a local spool writer. This process owns controller&lt;br /&gt;
credentials, learns only validated INBOX/Junk events, preserves every original,&lt;br /&gt;
and never retries a training operation with an uncertain result. Run foreground&lt;br /&gt;
under FreeBSD daemon(8), or use --once for one bounded batch.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import signal&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
import threading&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
_spec = importlib.util.spec_from_file_location(&amp;quot;feedback_worker_admin&amp;quot;, Path(__file__).with_name(&amp;quot;feedback-admin.py&amp;quot;))&lt;br /&gt;
admin = importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError = admin.FeedbackError&lt;br /&gt;
SHA_RE = re.compile(r&amp;quot;[0-9a-f]{64}\Z&amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS = 20000&lt;br /&gt;
UNCERTAIN = {&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learn_uncertain&amp;quot;, &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;quarantined_ordering&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def log_event(kind, **fields):&lt;br /&gt;
    # Callers pass only fixed diagnostics, numeric counts, event IDs and classes.&lt;br /&gt;
    print(json.dumps({&amp;quot;event&amp;quot;: kind, **fields}, ensure_ascii=True, sort_keys=True), flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _timestamp_ns(value):&lt;br /&gt;
    if not isinstance(value, str):&lt;br /&gt;
        raise ValueError(&amp;quot;missing timestamp&amp;quot;)&lt;br /&gt;
    parsed = datetime.fromisoformat(value.replace(&amp;quot;Z&amp;quot;, &amp;quot;+00:00&amp;quot;))&lt;br /&gt;
    if parsed.tzinfo is None:&lt;br /&gt;
        raise ValueError(&amp;quot;timestamp must have timezone&amp;quot;)&lt;br /&gt;
    return int(parsed.timestamp() * 1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def order_events(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read bounded metadata, ordered by collector created_ns, then event ID.&lt;br /&gt;
&lt;br /&gt;
    Legacy second-resolution timestamps are permitted only when the same&lt;br /&gt;
    content has no contradictory labels at an identical timestamp. Such a tie&lt;br /&gt;
    cannot be resolved from random event IDs and is quarantined without learning.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    pending = Path(root) / &amp;quot;pending&amp;quot;&lt;br /&gt;
    rows = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    with admin.Directory(pending):&lt;br /&gt;
        with os.scandir(pending) as entries:&lt;br /&gt;
            for entry in entries:&lt;br /&gt;
                if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                count += 1&lt;br /&gt;
                if count &amp;gt; admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
                    raise FeedbackError(&amp;quot;pending queue scan bound exceeded&amp;quot;)&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(pending / entry.name) as directory:&lt;br /&gt;
                        metadata = json.loads(directory.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    if not isinstance(metadata, dict):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid metadata&amp;quot;)&lt;br /&gt;
                    metadata = dict(metadata)&lt;br /&gt;
                    if metadata.get(&amp;quot;id&amp;quot;) != entry.name or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid event identity&amp;quot;)&lt;br /&gt;
                    if not isinstance(metadata.get(&amp;quot;sha256&amp;quot;), str) or not SHA_RE.fullmatch(metadata[&amp;quot;sha256&amp;quot;]):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid digest&amp;quot;)&lt;br /&gt;
                    admin.collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                                                   metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
                    timestamp = metadata.get(&amp;quot;created_ns&amp;quot;)&lt;br /&gt;
                    if isinstance(timestamp, int) and not isinstance(timestamp, bool) and timestamp &amp;gt; 0:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = timestamp&lt;br /&gt;
                    elif &amp;quot;created_ns&amp;quot; not in metadata:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = _timestamp_ns(metadata.get(&amp;quot;created_utc&amp;quot;))&lt;br /&gt;
                    else:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = 0&lt;br /&gt;
                        metadata[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
                    rows.append(metadata)&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    # A manual administrator may have archived this candidate.&lt;br /&gt;
                    continue&lt;br /&gt;
                except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                    rows.append({&amp;quot;id&amp;quot;: entry.name, &amp;quot;_invalid&amp;quot;: True, &amp;quot;_order_ns&amp;quot;: 0})&lt;br /&gt;
    tied = {}&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
            tied.setdefault((row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;]), set()).add(row[&amp;quot;label&amp;quot;])&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;) and len(tied[(row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;])]) &amp;gt; 1:&lt;br /&gt;
            row[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
    return sorted(rows, key=lambda row: (row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;id&amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _journal(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read root-owned receipts; successful classes use durable worker sequence.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    processed = Path(root) / &amp;quot;processed&amp;quot;&lt;br /&gt;
    latest, blocked = {}, set()&lt;br /&gt;
    max_sequence = used_bytes = count = 0&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
    admin.check_root_directory(processed)&lt;br /&gt;
    with os.scandir(processed) as entries:&lt;br /&gt;
        for entry in entries:&lt;br /&gt;
            if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                continue&lt;br /&gt;
            count += 1&lt;br /&gt;
            if count &amp;gt; MAX_JOURNAL_EVENTS:&lt;br /&gt;
                raise FeedbackError(&amp;quot;processed journal scan bound exceeded&amp;quot;)&lt;br /&gt;
            directory = processed / entry.name&lt;br /&gt;
            receipt = admin.receipt_for(directory)&lt;br /&gt;
            if not isinstance(receipt, dict) or receipt.get(&amp;quot;id&amp;quot;) != entry.name:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed receipt&amp;quot;)&lt;br /&gt;
            digest = receipt.get(&amp;quot;sha256&amp;quot;)&lt;br /&gt;
            if not isinstance(digest, str) or not SHA_RE.fullmatch(digest):&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed digest&amp;quot;)&lt;br /&gt;
            sequence = receipt.get(&amp;quot;worker_sequence&amp;quot;, 0)&lt;br /&gt;
            if not isinstance(sequence, int) or isinstance(sequence, bool) or sequence &amp;lt; 0:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed sequence&amp;quot;)&lt;br /&gt;
            max_sequence = max(max_sequence, sequence)&lt;br /&gt;
            status = receipt.get(&amp;quot;status&amp;quot;)&lt;br /&gt;
            if status in UNCERTAIN:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
            if status == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                label = receipt.get(&amp;quot;selected_class&amp;quot;)&lt;br /&gt;
                if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
                    raise FeedbackError(&amp;quot;invalid learned class&amp;quot;)&lt;br /&gt;
                # Existing manual receipts predate automatic processing; the old&lt;br /&gt;
                # admin refuses further training of a SHA once it has a receipt.&lt;br /&gt;
                fallback = _timestamp_ns(receipt.get(&amp;quot;finished_utc&amp;quot;, receipt.get(&amp;quot;reviewed_utc&amp;quot;)))&lt;br /&gt;
                position = (bool(sequence), sequence if sequence else fallback, entry.name)&lt;br /&gt;
                if digest not in latest or position &amp;gt; latest[digest][0]:&lt;br /&gt;
                    latest[digest] = (position, label)&lt;br /&gt;
            # Count snapshots and preserved originals, never recursively walk&lt;br /&gt;
            # arbitrary files in the mail-user-owned source/ directory.&lt;br /&gt;
            for part in (directory, directory / &amp;quot;source&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(part) as safe:&lt;br /&gt;
                        for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                            try:&lt;br /&gt;
                                if safe.fd is not None:&lt;br /&gt;
                                    info = os.stat(name, dir_fd=safe.fd, follow_symlinks=False)&lt;br /&gt;
                                else:&lt;br /&gt;
                                    info = (part / name).lstat()&lt;br /&gt;
                                if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                                    used_bytes += info.st_size&lt;br /&gt;
                            except FileNotFoundError:&lt;br /&gt;
                                continue&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    continue&lt;br /&gt;
    return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _check_trusted_parent_chain(path):&lt;br /&gt;
    if os.name != &amp;quot;posix&amp;quot;:&lt;br /&gt;
        return&lt;br /&gt;
    for parent in Path(path).parents:&lt;br /&gt;
        link_info = parent.lstat()&lt;br /&gt;
        info = parent.stat()&lt;br /&gt;
        if (link_info.st_uid != 0 or not stat.S_ISDIR(info.st_mode)&lt;br /&gt;
                or info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;executable parent directories must be controlled by root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def resolve_executable(executable):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Allow FreeBSD package symlinks, but pin both operations to their target.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    source = Path(executable)&lt;br /&gt;
    if not source.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be absolute&amp;quot;)&lt;br /&gt;
    source_info = source.lstat()&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and source_info.st_uid != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc entry must be owned by root&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(source)&lt;br /&gt;
    target = source.resolve(strict=True)&lt;br /&gt;
    info = target.lstat()&lt;br /&gt;
    if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be a regular file&amp;quot;)&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be root-owned and not writable by mail users&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(target)&lt;br /&gt;
    return str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def controller_ready(executable, controller, password_file, timeout):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Authenticated read-only controller probe; no candidate is archived yet.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    command = [resolve_executable(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;unsafe controller password file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;controller password file must be root-owned mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;stat&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        return subprocess.run(command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,&lt;br /&gt;
                              stderr=subprocess.DEVNULL, check=False, timeout=min(timeout, 5)).returncode == 0&lt;br /&gt;
    except (subprocess.TimeoutExpired, OSError):&lt;br /&gt;
        return False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def process_pending(root, *, executable=&amp;quot;/usr/local/bin/rspamc&amp;quot;, controller=&amp;quot;127.0.0.1:11334&amp;quot;,&lt;br /&gt;
                    password_file=admin.DEFAULT_PASSWORD_FILE, timeout=30, batch_size=20,&lt;br /&gt;
                    preflight=None, learner=None, stop_event=None):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Process at most batch_size candidates. Inject probes/learner for unit tests.&lt;br /&gt;
&lt;br /&gt;
    One admin lock serializes manual and background operations. A crash after a&lt;br /&gt;
    durable learning intent leaves status=learning, which blocks this SHA on the&lt;br /&gt;
    next pass; subsequent candidates are preserved as quarantined_uncertain.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    root = Path(root)&lt;br /&gt;
    preflight = controller_ready if preflight is None else preflight&lt;br /&gt;
    if learner is None:&lt;br /&gt;
        # FreeBSD pkg installs rspamc as a root-owned versioned symlink. Resolve&lt;br /&gt;
        # it once so preflight and training invoke the same trusted executable.&lt;br /&gt;
        executable = resolve_executable(executable)&lt;br /&gt;
        learner = admin.run_learning&lt;br /&gt;
    summary = {&amp;quot;learned&amp;quot;: 0, &amp;quot;duplicates&amp;quot;: 0, &amp;quot;quarantined&amp;quot;: 0, &amp;quot;invalid&amp;quot;: 0,&lt;br /&gt;
               &amp;quot;pending&amp;quot;: 0, &amp;quot;controller_unavailable&amp;quot;: False, &amp;quot;storage_blocked&amp;quot;: False,&lt;br /&gt;
               &amp;quot;archive_warning&amp;quot;: False}&lt;br /&gt;
    with admin.admin_lock(root):&lt;br /&gt;
        events = order_events(root)&lt;br /&gt;
        summary[&amp;quot;pending&amp;quot;] = len(events)&lt;br /&gt;
        if not events:&lt;br /&gt;
            return summary&lt;br /&gt;
        latest, blocked, sequence, used_bytes, archive_count = _journal(root)&lt;br /&gt;
        summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
        if used_bytes &amp;gt;= admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
            summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        if not preflight(executable, controller, password_file, timeout):&lt;br /&gt;
            summary[&amp;quot;controller_unavailable&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        handled = 0&lt;br /&gt;
        for candidate in events:&lt;br /&gt;
            if handled &amp;gt;= batch_size or (stop_event is not None and stop_event.is_set()):&lt;br /&gt;
                break&lt;br /&gt;
            if candidate.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            event_id = candidate[&amp;quot;id&amp;quot;]&lt;br /&gt;
            if (root / &amp;quot;processed&amp;quot; / event_id).exists():&lt;br /&gt;
                # Crash before prepare_archive moved pending/ID: its root-owned&lt;br /&gt;
                # intent is authoritative. Preserve the pending original for&lt;br /&gt;
                # manual recovery and continue independent content, never retry.&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            try:&lt;br /&gt;
                metadata, message = admin.read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
            except FileNotFoundError:&lt;br /&gt;
                continue&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            # Do not act if the queued ordering/identity changed since enumeration.&lt;br /&gt;
            if any(metadata.get(key) != candidate.get(key) for key in&lt;br /&gt;
                   (&amp;quot;sha256&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;created_ns&amp;quot;, &amp;quot;created_utc&amp;quot;)):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            if used_bytes + 2 * len(message) + 32768 &amp;gt; admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            label, digest = metadata[&amp;quot;label&amp;quot;], metadata[&amp;quot;sha256&amp;quot;]&lt;br /&gt;
            if candidate.get(&amp;quot;_ordering_invalid&amp;quot;):&lt;br /&gt;
                status, reason = &amp;quot;quarantined_ordering&amp;quot;, &amp;quot;event ordering is ambiguous&amp;quot;&lt;br /&gt;
            elif digest in blocked:&lt;br /&gt;
                status, reason = &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;a prior learning operation has an uncertain result&amp;quot;&lt;br /&gt;
            elif digest in latest and latest[digest][1] == label:&lt;br /&gt;
                status, reason = &amp;quot;duplicate&amp;quot;, &amp;quot;same class as the latest successful learning operation&amp;quot;&lt;br /&gt;
            else:&lt;br /&gt;
                status, reason = &amp;quot;learning&amp;quot;, &amp;quot;automatic explicit IMAP feedback&amp;quot;&lt;br /&gt;
            try:&lt;br /&gt;
                archive, receipt = admin.prepare_archive(root, event_id, metadata, message, status, label, reason)&lt;br /&gt;
            except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                # prepare_archive writes a root journal before moving the source;&lt;br /&gt;
                # stop instead of retrying an incompletely prepared operation.&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            sequence += 1&lt;br /&gt;
            receipt.update({&amp;quot;mode&amp;quot;: &amp;quot;automatic_imap_feedback&amp;quot;, &amp;quot;worker_sequence&amp;quot;: sequence,&lt;br /&gt;
                            &amp;quot;processed_ns&amp;quot;: time.time_ns(), &amp;quot;source_created_ns&amp;quot;: candidate[&amp;quot;_order_ns&amp;quot;]})&lt;br /&gt;
            if digest in latest:&lt;br /&gt;
                receipt[&amp;quot;previous_successful_class&amp;quot;] = latest[digest][1]&lt;br /&gt;
            # Durable intent exists before the only call that may change Bayes.&lt;br /&gt;
            admin.update_receipt(archive, receipt)&lt;br /&gt;
            if status == &amp;quot;learning&amp;quot;:&lt;br /&gt;
                try:&lt;br /&gt;
                    returncode = learner(message, label, executable=executable, controller=controller,&lt;br /&gt;
                                         password_file=password_file, timeout=timeout)&lt;br /&gt;
                except (subprocess.TimeoutExpired, OSError, FeedbackError):&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    receipt[&amp;quot;error&amp;quot;] = &amp;quot;client failure; server may have learned; no automatic retry&amp;quot;&lt;br /&gt;
                else:&lt;br /&gt;
                    receipt[&amp;quot;returncode&amp;quot;] = returncode&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if returncode == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    if returncode != 0:&lt;br /&gt;
                        receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; no automatic retry&amp;quot;&lt;br /&gt;
                receipt[&amp;quot;finished_utc&amp;quot;] = admin.collector.utc_now()&lt;br /&gt;
                admin.update_receipt(archive, receipt)&lt;br /&gt;
                if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                    latest[digest] = ((True, sequence, event_id), label)&lt;br /&gt;
                    summary[&amp;quot;learned&amp;quot;] += 1&lt;br /&gt;
                else:&lt;br /&gt;
                    blocked.add(digest)&lt;br /&gt;
                    summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            elif status == &amp;quot;duplicate&amp;quot;:&lt;br /&gt;
                summary[&amp;quot;duplicates&amp;quot;] += 1&lt;br /&gt;
            else:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
                summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            handled += 1&lt;br /&gt;
            used_bytes += 2 * len(message) + 32768&lt;br /&gt;
            archive_count += 1&lt;br /&gt;
            summary[&amp;quot;pending&amp;quot;] -= 1&lt;br /&gt;
            summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
            log_event(&amp;quot;feedback_processed&amp;quot;, id=event_id, label=label, status=receipt[&amp;quot;status&amp;quot;])&lt;br /&gt;
            if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learn_uncertain&amp;quot;:&lt;br /&gt;
                # The controller may have failed after the preflight. Leave all&lt;br /&gt;
                # other candidates pending until a fresh read-only probe passes.&lt;br /&gt;
                break&lt;br /&gt;
    return summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=admin.DEFAULT_ROOT)&lt;br /&gt;
    parser.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--password-file&amp;quot;, default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
    parser.add_argument(&amp;quot;--poll&amp;quot;, type=float, default=2)&lt;br /&gt;
    parser.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    parser.add_argument(&amp;quot;--batch-size&amp;quot;, type=int, default=20)&lt;br /&gt;
    parser.add_argument(&amp;quot;--once&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    admin.require_root()&lt;br /&gt;
    root = Path(args.queue_root)&lt;br /&gt;
    if not root.is_absolute() or not 0.2 &amp;lt;= args.poll &amp;lt;= 60 or not 0 &amp;lt; args.timeout &amp;lt;= 300 or not 1 &amp;lt;= args.batch_size &amp;lt;= 100:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid worker options&amp;quot;)&lt;br /&gt;
    admin.check_root_directory(root)&lt;br /&gt;
    stopping = threading.Event()&lt;br /&gt;
    for signum in (signal.SIGTERM, signal.SIGINT):&lt;br /&gt;
        signal.signal(signum, lambda *_: stopping.set())&lt;br /&gt;
    lockfd = os.open(root / &amp;quot;.worker.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)&lt;br /&gt;
        previous_state = None&lt;br /&gt;
        while not stopping.is_set():&lt;br /&gt;
            try:&lt;br /&gt;
                summary = process_pending(root, executable=args.rspamc_bin, controller=args.controller,&lt;br /&gt;
                                          password_file=args.password_file, timeout=args.timeout,&lt;br /&gt;
                                          batch_size=args.batch_size, stop_event=stopping)&lt;br /&gt;
                state = {key: value for key, value in summary.items() if key not in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)}&lt;br /&gt;
                if state != previous_state or any(summary[key] for key in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)):&lt;br /&gt;
                    log_event(&amp;quot;worker_state&amp;quot;, **summary)&lt;br /&gt;
                previous_state = state&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 2 if summary[&amp;quot;controller_unavailable&amp;quot;] or summary[&amp;quot;storage_blocked&amp;quot;] else 0&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                if previous_state != &amp;quot;local_error&amp;quot;:&lt;br /&gt;
                    log_event(&amp;quot;worker_error&amp;quot;, reason=&amp;quot;local queue, journal or configuration failure; no automatic retry of uncertain training&amp;quot;)&lt;br /&gt;
                previous_state = &amp;quot;local_error&amp;quot;&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 1&lt;br /&gt;
            stopping.wait(args.poll)&lt;br /&gt;
        return 0&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    try:&lt;br /&gt;
        raise SystemExit(main())&lt;br /&gt;
    except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
        log_event(&amp;quot;worker_start_failed&amp;quot;, reason=&amp;quot;root, configuration or worker lock required&amp;quot;)&lt;br /&gt;
        raise SystemExit(1)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Новые письма с локальной пометкой спама автоматически доставляются в Junk прочитанными; обычная почта во Входящих получает прежние флаги. Чтобы повторить настройку, после сохранения приведённого выше spam-delivery.sieve добавить &amp;lt;code&amp;gt;&amp;quot;imap4flags&amp;quot;&amp;lt;/code&amp;gt; в список расширений require. Внутри условия &amp;lt;code&amp;gt;X-Spam-Flag: YES&amp;lt;/code&amp;gt;, непосредственно перед &amp;lt;code&amp;gt;fileinto :create &amp;quot;Junk&amp;quot;;&amp;lt;/code&amp;gt;, добавить строку &amp;lt;code&amp;gt;addflag &amp;quot;\\Seen&amp;quot;;&amp;lt;/code&amp;gt;. Действие addflag добавляет флаг прочтения и сохраняет остальные флаги письма. После изменения повторно скомпилировать spam-delivery.sieve командой ниже. Уже находящиеся в папках письма не изменяются. SMTP-отклонение спама не включается; автоматическая доставка не запускает обучение. [https://www.rfc-editor.org/rfc/rfc5232 Расширение Sieve imap4flags].&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== 16. Перенос почты с Яндекса через imapsync ==&lt;br /&gt;
&lt;br /&gt;
Однократное копирование переносит письма из существующего ящика Яндекса в существующий ящик Dovecot. Пример ниже использует &amp;lt;code&amp;gt;source@example.org&amp;lt;/code&amp;gt; как источник и &amp;lt;code&amp;gt;target@example.net&amp;lt;/code&amp;gt; на &amp;lt;code&amp;gt;mail.example.net&amp;lt;/code&amp;gt; как назначение. Замените эти значения своими. Переключать MX обратно на Яндекс для такого переноса не требуется.&lt;br /&gt;
&lt;br /&gt;
Папки источника размещаются внутри отдельной папки &amp;lt;code&amp;gt;Yandex-source&amp;lt;/code&amp;gt;. Оригиналы на Яндексе и текущая почта назначения сохраняются. Команды выполняются последовательно в одной интерактивной root-сессии SSH; после переподключения параметры &amp;lt;code&amp;gt;set --&amp;lt;/code&amp;gt; нужно задать заново.&lt;br /&gt;
&lt;br /&gt;
=== 16.1. Доступ и пакеты ===&lt;br /&gt;
&lt;br /&gt;
Разрешите IMAP в настройках почтовых программ Яндекса; для организации Яндекс 360 может потребоваться также разрешение протокола в кабинете администратора. Для источника используйте пароль приложения с доступом к Почте, для назначения — пароль своего ящика Dovecot. Если Яндекс блокирует IMAP и разрешить его нельзя, imapsync не сможет прочитать письма.&lt;br /&gt;
&lt;br /&gt;
Проверьте, что &amp;lt;code&amp;gt;/opt&amp;lt;/code&amp;gt; смонтирован как UFS, целевой ящик уже существует и имя &amp;lt;code&amp;gt;mail.example.net&amp;lt;/code&amp;gt; соответствует действительному сертификату почтового сервера. Импорт выполняется в обычное хранилище IMAP; файловый каталог пользователей Exim/Dovecot менять не требуется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
sudo pkg install imapsync&lt;br /&gt;
sudo /bin/sh&lt;br /&gt;
umask 077&lt;br /&gt;
install -d -o root -g wheel -m 0700 /opt/mail/migration/yandex-source&lt;br /&gt;
install -d -o root -g wheel -m 0700 /opt/mail/migration/yandex-source/cache /opt/mail/migration/yandex-source/log&lt;br /&gt;
cd /opt/mail/migration/yandex-source&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 16.2. Ввести пароли без отображения ===&lt;br /&gt;
&lt;br /&gt;
Следующие команды запрашивают пароли в терминале и записывают их в новые файлы. Пароли не попадают в аргументы процессов, переменные окружения или историю shell. С установленной выше маской файлы имеют права &amp;lt;code&amp;gt;0600&amp;lt;/code&amp;gt;, каталог — &amp;lt;code&amp;gt;0700&amp;lt;/code&amp;gt;. Режим создания &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; отказывается перезаписывать существующие файлы; не запускайте команды повторно, если пароли уже введены.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
python3.12 -c &#039;import getpass, os, sys; assert os.geteuid() == 0 and sys.stdin.isatty(); p = getpass.getpass(&amp;quot;Пароль приложения Яндекса: &amp;quot;); assert p and not any(c in p for c in &amp;quot;\x00\r\n&amp;quot;); f = open(&amp;quot;source.pass&amp;quot;, &amp;quot;x&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;); f.write(p + &amp;quot;\n&amp;quot;); f.close()&#039;&lt;br /&gt;
python3.12 -c &#039;import getpass, os, sys; assert os.geteuid() == 0 and sys.stdin.isatty(); p = getpass.getpass(&amp;quot;Пароль целевого ящика: &amp;quot;); assert p and not any(c in p for c in &amp;quot;\x00\r\n&amp;quot;); f = open(&amp;quot;target.pass&amp;quot;, &amp;quot;x&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;); f.write(p + &amp;quot;\n&amp;quot;); f.close()&#039;&lt;br /&gt;
stat -f &#039;%Su:%Sg %Lp %N&#039; source.pass target.pass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 16.3. Задать параметры и проверить план ===&lt;br /&gt;
&lt;br /&gt;
В примере включена проверка цепочки сертификатов и имён обоих серверов, разрешён TLS 1.2 и новее. Даты доставки и флаги новых писем копируются; повторный запуск не перезаписывает флаги уже перенесённых сообщений. &amp;lt;code&amp;gt;--syncduplicates&amp;lt;/code&amp;gt; сохраняет также повторяющиеся экземпляры писем источника. Не меняйте способ сопоставления сообщений между запусками одного переноса.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
set -- \&lt;br /&gt;
  --host1 imap.yandex.ru --port1 993 --ssl1 \&lt;br /&gt;
  --user1 source@example.org --passfile1 /opt/mail/migration/yandex-source/source.pass \&lt;br /&gt;
  --sslargs1 SSL_verify_mode=1 \&lt;br /&gt;
  --sslargs1 SSL_verifycn_scheme=imap \&lt;br /&gt;
  --sslargs1 SSL_verifycn_name=imap.yandex.ru \&lt;br /&gt;
  --sslargs1 SSL_hostname=imap.yandex.ru \&lt;br /&gt;
  --sslargs1 SSL_ca_file=/etc/ssl/cert.pem \&lt;br /&gt;
  --sslargs1 SSL_version=SSLv23:!SSLv3:!SSLv2:!TLSv1:!TLSv1_1 \&lt;br /&gt;
  --host2 mail.example.net --port2 993 --ssl2 \&lt;br /&gt;
  --user2 target@example.net --passfile2 /opt/mail/migration/yandex-source/target.pass \&lt;br /&gt;
  --sslargs2 SSL_verify_mode=1 \&lt;br /&gt;
  --sslargs2 SSL_verifycn_scheme=imap \&lt;br /&gt;
  --sslargs2 SSL_verifycn_name=mail.example.net \&lt;br /&gt;
  --sslargs2 SSL_hostname=mail.example.net \&lt;br /&gt;
  --sslargs2 SSL_ca_file=/etc/ssl/cert.pem \&lt;br /&gt;
  --sslargs2 SSL_version=SSLv23:!SSLv3:!SSLv2:!TLSv1:!TLSv1_1 \&lt;br /&gt;
  --subfolder2 Yandex-source \&lt;br /&gt;
  --useheader Message-Id --syncduplicates --syncinternaldates --noresyncflags \&lt;br /&gt;
  --noexpunge1 --noexpunge2 --nouidexpunge2 \&lt;br /&gt;
  --noemailreport1 --noemailreport2 --noreleasecheck \&lt;br /&gt;
  --tmpdir /opt/mail/migration/yandex-source/cache \&lt;br /&gt;
  --logdir /opt/mail/migration/yandex-source/log \&lt;br /&gt;
  --pidfile /opt/mail/migration/yandex-source/imapsync.pid --pidfilelocking&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверки ниже не копируют письма. Первая должна подтвердить вход в оба ящика; следующая показывает соответствие папок, затем выводятся количество и объём писем. После любой ошибки сначала устраните её и повторите проверку, прежде чем продолжать.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --justlogin --logfile login.log&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --dry --justfolders --logfile folders.log&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --dry --justfoldersizes --logfile sizes.log&lt;br /&gt;
df -h /opt&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сопоставьте суммарный объём источника из &amp;lt;code&amp;gt;sizes.log&amp;lt;/code&amp;gt; со свободным местом &amp;lt;code&amp;gt;df&amp;lt;/code&amp;gt;. Для первого полного копирования оставьте запас не менее 1 ГиБ сверх объёма переносимых писем, а для большого числа маленьких писем — дополнительное место под файловые метаданные. Если места недостаточно, не начинайте копирование: сначала увеличьте раздел или уменьшите согласованный объём переноса. В выводе плана все создаваемые папки должны находиться под &amp;lt;code&amp;gt;Yandex-source&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== 16.4. Скопировать и проверить результат ===&lt;br /&gt;
&lt;br /&gt;
Во время ручного запуска держите SSH-сессию открытой. Не запускайте два переноса этой пары одновременно; файл PID дополнительно защищает от параллельного старта. Не добавляйте параметры удаления &amp;lt;code&amp;gt;--delete1&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;--delete2&amp;lt;/code&amp;gt; или удаления папок. Включённые &amp;lt;code&amp;gt;--noexpunge*&amp;lt;/code&amp;gt; запрещают очистку удалённых сообщений.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --logfile copy.log&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После успешного завершения выполните повторную проверку с теми же параметрами:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --dry --logfile verify.log&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверьте код завершения &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, отсутствие ошибок в итогах imapsync и отсутствие новых писем, предлагаемых к копированию в проверочном запуске. Если источник получил новые сообщения во время переноса, повторите обычное копирование с теми же параметрами и затем снова проверку. Откройте папку &amp;lt;code&amp;gt;Yandex-source&amp;lt;/code&amp;gt; и её подпапки в SnappyMail; выборочно сравните письма, вложения, даты и состояние прочитано/непрочитано с источником.&lt;br /&gt;
&lt;br /&gt;
Если перенос прервался, журналы и cache остаются в защищённом каталоге. После устранения причины повторите команду с прежними параметрами и cache. Перед перезапуском убедитесь, что предыдущий процесс завершён; наличие старого PID-файла само по себе не доказывает, что процесс ещё работает.&lt;br /&gt;
&lt;br /&gt;
После окончательной проверки и завершения всех процессов удалите временные файлы паролей:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
rm -f -- /opt/mail/migration/yandex-source/source.pass /opt/mail/migration/yandex-source/target.pass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Затем отзовите использованный пароль приложения Яндекса, если он был создан только для миграции. Сохранённые журналы доступны root и могут содержать адреса и названия папок; не публикуйте их без обезличивания.&lt;br /&gt;
&lt;br /&gt;
=== 16.5. Ошибки доступа ===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Invalid credentials or IMAP is disabled&amp;lt;/code&amp;gt;: проверьте полный адрес источника, пароль приложения и разрешение IMAP у пользователя и организации. Обычный пароль аккаунта может не подходить для почтового клиента.&lt;br /&gt;
* &amp;lt;code&amp;gt;To use Yandex Mail in other email clients, you need a Yandex 360 for Business plan&amp;lt;/code&amp;gt;: доступ к IMAP ограничен условиями тарифа Яндекса. Сначала восстановите доступ на стороне Яндекса; изменение параметров Dovecot эту причину не устраняет.&lt;br /&gt;
* Ошибка сертификата: исправьте имя сервера, время или цепочку сертификатов. Не отключайте &amp;lt;code&amp;gt;SSL_verify_mode&amp;lt;/code&amp;gt; и проверку имени.&lt;br /&gt;
* Недостаточно места: остановите перенос, освободите или увеличьте хранилище и повторите с прежними параметрами. Не удаляйте исходные письма ради освобождения места на назначении.&lt;br /&gt;
&lt;br /&gt;
Источники: [https://imapsync.lamiral.info/README руководство imapsync], [https://imapsync.lamiral.info/FAQ.d/FAQ.Yandex.txt параметры Яндекса], [https://imapsync.lamiral.info/FAQ.d/FAQ.Duplicates.txt дубликаты], [https://yandex.ru/support/yandex-360/business/admin/ru/mail/email-clients/ разрешение почтовых протоколов].&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=693</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=693"/>
		<updated>2026-10-30T01:38:04Z</updated>

		<summary type="html">&lt;p&gt;Bas: Уточнена диагностика отказа IMAP Яндекса из-за тарифа организации.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет → TCP 25 / STARTTLS → Exim&lt;br /&gt;
Клиент   → TCP 465 TLS или 587 STARTTLS + AUTH → Exim&lt;br /&gt;
Exim     → LMTP Unix socket → Dovecot → /opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim     → AUTH Unix socket → Dovecot → файловый каталог пользователей&lt;br /&gt;
Клиент   → TCP 993 TLS → Dovecot&lt;br /&gt;
SnappyMail (необязательно) → те же IMAPS и SMTP AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
sysrc hostname=mail.example.org&lt;br /&gt;
hostname mail.example.org&lt;br /&gt;
sysrc defaultrouter=198.51.100.1&lt;br /&gt;
# Только если в работающей таблице отсутствует default route:&lt;br /&gt;
route add default 198.51.100.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo /dev/da1 | awk &#039;{print $3}&#039;)&lt;br /&gt;
test -n &amp;quot;$disk_bytes&amp;quot; &amp;amp;&amp;amp; test &amp;quot;$disk_bytes&amp;quot; -gt 0 || exit 1&lt;br /&gt;
cmp -s -n &amp;quot;$disk_bytes&amp;quot; /dev/da1 /dev/zero&lt;br /&gt;
printf &#039;cmp status: %s\n&#039; &amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
pkg update&lt;br /&gt;
pkg install -n exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
# После проверки плана:&lt;br /&gt;
pkg install -y exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
sysrc exim_enable=NO dovecot_enable=NO&lt;br /&gt;
sysrc sendmail_enable=NONE sendmail_submit_enable=NO&lt;br /&gt;
sysrc sendmail_outbound_enable=NO sendmail_msp_queue_enable=NO&lt;br /&gt;
service sendmail onestop&lt;br /&gt;
/usr/local/sbin/exim -bV&lt;br /&gt;
/usr/local/sbin/dovecot --version&lt;br /&gt;
pw usershow mailnull&lt;br /&gt;
pw groupshow mail&lt;br /&gt;
pw groupshow dovecot&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only review and explicit training of IMAPSieve feedback.&lt;br /&gt;
&lt;br /&gt;
No command trains automatically. list/show are local reads. learn ID spam|ham&lt;br /&gt;
is the only command that invokes rspamc. reject ID archives without training.&lt;br /&gt;
Queue root and processed must be root-owned, non-writable by mail users;&lt;br /&gt;
pending is separately owned by vmail, mode 0700. Archived originals remain in&lt;br /&gt;
processed/ID/source; only root-owned snapshots are used for training or review.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from contextlib import contextmanager&lt;br /&gt;
from email import policy&lt;br /&gt;
from email.parser import BytesHeaderParser&lt;br /&gt;
import hashlib&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import shutil&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
&lt;br /&gt;
spec = importlib.util.spec_from_file_location(&amp;quot;feedback_collector&amp;quot;, Path(__file__).with_name(&amp;quot;feedback.py&amp;quot;))&lt;br /&gt;
collector = importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE = collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError = collector.FeedbackError&lt;br /&gt;
ID_RE = re.compile(r&amp;quot;[0-9a-f]{32}\Z&amp;quot;)&lt;br /&gt;
DEFAULT_ROOT = &amp;quot;/opt/mail/rspamd-feedback&amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE = &amp;quot;/usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
HEADERS = (&amp;quot;From&amp;quot;, &amp;quot;To&amp;quot;, &amp;quot;Date&amp;quot;, &amp;quot;Subject&amp;quot;, &amp;quot;Message-ID&amp;quot;, &amp;quot;X-Spam-Status&amp;quot;, &amp;quot;X-Spam-Score&amp;quot;, &amp;quot;X-Rspamd-Action&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def emit(value):&lt;br /&gt;
    print(json.dumps(value, ensure_ascii=True, sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def require_root():&lt;br /&gt;
    if not hasattr(os, &amp;quot;geteuid&amp;quot;) or os.geteuid() != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;this administrative command requires root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_id(event_id):&lt;br /&gt;
    if not ID_RE.fullmatch(event_id):&lt;br /&gt;
        raise FeedbackError(&amp;quot;ID must be exactly 32 lowercase hexadecimal characters&amp;quot;)&lt;br /&gt;
    return event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Directory:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Nofollow directory descriptor on FreeBSD; portable fallback for local tests.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, path):&lt;br /&gt;
        self.path = Path(path)&lt;br /&gt;
        info = self.path.lstat()&lt;br /&gt;
        if not stat.S_ISDIR(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;expected a real directory&amp;quot;)&lt;br /&gt;
        self.fd = None&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            self.fd = os.open(self.path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
    def __enter__(self):&lt;br /&gt;
        return self&lt;br /&gt;
&lt;br /&gt;
    def __exit__(self, *args):&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
    def read(self, name, limit):&lt;br /&gt;
        if &amp;quot;/&amp;quot; in name or &amp;quot;\\&amp;quot; in name or name in (&amp;quot;.&amp;quot;, &amp;quot;..&amp;quot;):&lt;br /&gt;
            raise FeedbackError(&amp;quot;invalid internal filename&amp;quot;)&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=self.fd)&lt;br /&gt;
        else:&lt;br /&gt;
            path = self.path / name&lt;br /&gt;
            if path.is_symlink():&lt;br /&gt;
                raise FeedbackError(&amp;quot;symlink is not an event file&amp;quot;)&lt;br /&gt;
            fd = os.open(path, os.O_RDONLY | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0))&lt;br /&gt;
        with os.fdopen(fd, &amp;quot;rb&amp;quot;) as source:&lt;br /&gt;
            info = os.fstat(source.fileno())&lt;br /&gt;
            if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_size &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file is not a bounded, single-link regular file&amp;quot;)&lt;br /&gt;
            result = source.read(limit + 1)&lt;br /&gt;
            if len(result) &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file exceeded the size limit&amp;quot;)&lt;br /&gt;
            return result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def read_event(directory, event_id):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    # On POSIX open the event relative to the already opened parent descriptor.&lt;br /&gt;
    # The parent path is deployment-owned, and a symlink event is rejected.&lt;br /&gt;
    with Directory(Path(directory) / event_id) as entry:&lt;br /&gt;
        metadata = json.loads(entry.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
        message = entry.read(&amp;quot;message.eml&amp;quot;, MAX_MESSAGE)&lt;br /&gt;
    if not isinstance(metadata, dict) or not isinstance(metadata.get(&amp;quot;user&amp;quot;), str):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                             metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
    if metadata.get(&amp;quot;id&amp;quot;) != event_id or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    if metadata.get(&amp;quot;bytes&amp;quot;) != len(message) or metadata.get(&amp;quot;sha256&amp;quot;) != hashlib.sha256(message).hexdigest():&lt;br /&gt;
        raise FeedbackError(&amp;quot;event content does not match its metadata&amp;quot;)&lt;br /&gt;
    return metadata, message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def write_private(path, value, *, json_value=False):&lt;br /&gt;
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0)&lt;br /&gt;
    fd = os.open(path, flags, 0o600)&lt;br /&gt;
    with os.fdopen(fd, &amp;quot;wb&amp;quot;) as output:&lt;br /&gt;
        if json_value:&lt;br /&gt;
            value = (json.dumps(value, ensure_ascii=True, sort_keys=True) + &amp;quot;\n&amp;quot;).encode()&lt;br /&gt;
        output.write(value)&lt;br /&gt;
        output.flush()&lt;br /&gt;
        os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def update_receipt(directory, receipt):&lt;br /&gt;
    temporary = directory / &amp;quot;.admin-new.json&amp;quot;&lt;br /&gt;
    write_private(temporary, receipt, json_value=True)&lt;br /&gt;
    os.replace(temporary, directory / &amp;quot;admin.json&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_root_directory(path):&lt;br /&gt;
    with Directory(path):&lt;br /&gt;
        info = Path(path).stat()&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;administrative directories must be root-owned and not group/other writable&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def admin_lock(root):&lt;br /&gt;
    check_root_directory(root)&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        processed.mkdir(mode=0o700)&lt;br /&gt;
    check_root_directory(processed)&lt;br /&gt;
    fd = os.open(root / &amp;quot;.admin.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(fd, fcntl.LOCK_EX)&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def receipt_for(directory):&lt;br /&gt;
    with Directory(directory) as entry:&lt;br /&gt;
        return json.loads(entry.read(&amp;quot;admin.json&amp;quot;, 16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def find_prior(root, sha256):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return None&lt;br /&gt;
    for entry in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
            continue&lt;br /&gt;
        try:&lt;br /&gt;
            receipt = receipt_for(entry)&lt;br /&gt;
        except (OSError, ValueError, FeedbackError):&lt;br /&gt;
            # A damaged journal must never silently allow repeated training.&lt;br /&gt;
            raise FeedbackError(&amp;quot;processed journal is incomplete; inspect it before more training&amp;quot;)&lt;br /&gt;
        if receipt.get(&amp;quot;sha256&amp;quot;) == sha256 and receipt.get(&amp;quot;status&amp;quot;) in (&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learned&amp;quot;, &amp;quot;learn_uncertain&amp;quot;):&lt;br /&gt;
            return receipt&lt;br /&gt;
    return None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def prepare_archive(root, event_id, metadata, message, status, label=None, reason=None):&lt;br /&gt;
    if shutil.disk_usage(root).free &amp;lt; collector.MIN_FREE_BYTES + len(message) + 32768:&lt;br /&gt;
        raise FeedbackError(&amp;quot;insufficient free disk space to preserve the reviewed snapshot&amp;quot;)&lt;br /&gt;
    archive = root / &amp;quot;processed&amp;quot; / event_id&lt;br /&gt;
    archive.mkdir(mode=0o700)&lt;br /&gt;
    receipt = {&amp;quot;schema&amp;quot;: 1, &amp;quot;id&amp;quot;: event_id, &amp;quot;sha256&amp;quot;: metadata[&amp;quot;sha256&amp;quot;],&lt;br /&gt;
               &amp;quot;reviewed_utc&amp;quot;: collector.utc_now(), &amp;quot;status&amp;quot;: &amp;quot;preparing&amp;quot;,&lt;br /&gt;
               &amp;quot;selected_class&amp;quot;: label, &amp;quot;reason&amp;quot;: reason}&lt;br /&gt;
    write_private(archive / &amp;quot;admin.json&amp;quot;, receipt, json_value=True)&lt;br /&gt;
    write_private(archive / &amp;quot;message.eml&amp;quot;, message)&lt;br /&gt;
    write_private(archive / &amp;quot;metadata.json&amp;quot;, json.dumps(metadata, ensure_ascii=True).encode())&lt;br /&gt;
    # Keep the complete original event. Never traverse or execute files in source/.&lt;br /&gt;
    os.rename(root / &amp;quot;pending&amp;quot; / event_id, archive / &amp;quot;source&amp;quot;)&lt;br /&gt;
    receipt[&amp;quot;status&amp;quot;] = status&lt;br /&gt;
    update_receipt(archive, receipt)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;pending&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;processed&amp;quot;)&lt;br /&gt;
    return archive, receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def run_learning(message, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    executable = Path(executable)&lt;br /&gt;
    if not executable.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be an absolute path&amp;quot;)&lt;br /&gt;
    command = [str(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be an absolute regular file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be root-owned, mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        # rspamc reads the file itself; the secret never appears in argv or logs.&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;learn_&amp;quot; + label)&lt;br /&gt;
    # No -u/-d: one shared Bayes classifier. No shell and no automatic retry.&lt;br /&gt;
    return subprocess.run(command, input=message, stdout=subprocess.DEVNULL,&lt;br /&gt;
                          stderr=subprocess.DEVNULL, timeout=timeout, check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def learn_event(root, event_id, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid training class&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        prior = find_prior(root, metadata[&amp;quot;sha256&amp;quot;])&lt;br /&gt;
        if prior:&lt;br /&gt;
            raise FeedbackError(&amp;quot;this content has a prior learned or uncertain event; inspect it and reject the duplicate&amp;quot;)&lt;br /&gt;
        archive, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;learning&amp;quot;, label)&lt;br /&gt;
        try:&lt;br /&gt;
            rc = run_learning(message, label, executable=executable, controller=controller,&lt;br /&gt;
                              password_file=password_file, timeout=timeout)&lt;br /&gt;
        except subprocess.TimeoutExpired:&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client timeout; server may have learned; do not automatically retry&amp;quot;&lt;br /&gt;
        except (OSError, FeedbackError):&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_failed&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client could not start or local configuration was invalid&amp;quot;&lt;br /&gt;
        else:&lt;br /&gt;
            receipt[&amp;quot;returncode&amp;quot;] = rc&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if rc == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            if rc:&lt;br /&gt;
                receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; inspect server before any retry&amp;quot;&lt;br /&gt;
        receipt[&amp;quot;finished_utc&amp;quot;] = collector.utc_now()&lt;br /&gt;
        update_receipt(archive, receipt)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def reject_event(root, event_id, reason=&amp;quot;review rejected&amp;quot;):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if len(reason) &amp;gt; 500:&lt;br /&gt;
        raise FeedbackError(&amp;quot;reason is too long&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        _, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;rejected&amp;quot;, reason=reason)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def list_events(root, state, limit):&lt;br /&gt;
    count = 0&lt;br /&gt;
    for name in ((&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;) if state == &amp;quot;all&amp;quot; else (state,)):&lt;br /&gt;
        directory = root / name&lt;br /&gt;
        if not directory.exists():&lt;br /&gt;
            continue&lt;br /&gt;
        with Directory(directory):&lt;br /&gt;
            for entry in directory.iterdir():&lt;br /&gt;
                if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                if count &amp;gt;= limit:&lt;br /&gt;
                    emit({&amp;quot;notice&amp;quot;: &amp;quot;limit reached&amp;quot;, &amp;quot;limit&amp;quot;: limit})&lt;br /&gt;
                    return&lt;br /&gt;
                try:&lt;br /&gt;
                    with Directory(entry) as item:&lt;br /&gt;
                        metadata = json.loads(item.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    row = {key: metadata.get(key) for key in (&amp;quot;id&amp;quot;, &amp;quot;created_utc&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;user&amp;quot;, &amp;quot;source&amp;quot;, &amp;quot;destination&amp;quot;, &amp;quot;bytes&amp;quot;)}&lt;br /&gt;
                    row[&amp;quot;state&amp;quot;] = name&lt;br /&gt;
                    if name == &amp;quot;processed&amp;quot;:&lt;br /&gt;
                        row[&amp;quot;review&amp;quot;] = receipt_for(entry)&lt;br /&gt;
                    emit(row)&lt;br /&gt;
                except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                    emit({&amp;quot;id&amp;quot;: entry.name, &amp;quot;state&amp;quot;: name, &amp;quot;error&amp;quot;: &amp;quot;invalid event; manual inspection required&amp;quot;})&lt;br /&gt;
                count += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def show_event(root, event_id, state, path_only):&lt;br /&gt;
    metadata, message = read_event(root / state, check_id(event_id))&lt;br /&gt;
    if path_only:&lt;br /&gt;
        emit({&amp;quot;id&amp;quot;: event_id, &amp;quot;message_path&amp;quot;: str(root / state / event_id / &amp;quot;message.eml&amp;quot;)})&lt;br /&gt;
        return&lt;br /&gt;
    headers = BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message), 262144)])&lt;br /&gt;
    emit({&amp;quot;metadata&amp;quot;: metadata, &amp;quot;headers&amp;quot;: {name: [str(v)[:2000] for v in headers.get_all(name, [])[:10]] for name in HEADERS}})&lt;br /&gt;
    if state == &amp;quot;processed&amp;quot;:&lt;br /&gt;
        emit({&amp;quot;review&amp;quot;: receipt_for(root / state / event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def warn_processed_storage(root):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return&lt;br /&gt;
    total = count = 0&lt;br /&gt;
    for event in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(event.name) or event.is_symlink() or not event.is_dir():&lt;br /&gt;
            continue&lt;br /&gt;
        count += 1&lt;br /&gt;
        for prefix in (event, event / &amp;quot;source&amp;quot;):&lt;br /&gt;
            if prefix.is_symlink():&lt;br /&gt;
                continue&lt;br /&gt;
            for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    info = (prefix / name).lstat()&lt;br /&gt;
                    if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                        total += info.st_size&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    pass&lt;br /&gt;
    if total &amp;gt;= collector.MAX_PENDING_BYTES or count &amp;gt;= collector.MAX_PENDING_EVENTS:&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: WARNING processed archive reached 512 MiB or 1024 events; review retention manually; nothing deleted&amp;quot;, file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=DEFAULT_ROOT)&lt;br /&gt;
    sub = parser.add_subparsers(dest=&amp;quot;command&amp;quot;, required=True)&lt;br /&gt;
    listing = sub.add_parser(&amp;quot;list&amp;quot;, help=&amp;quot;JSON lines, metadata only&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;, &amp;quot;all&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--limit&amp;quot;, type=int, default=100)&lt;br /&gt;
    showing = sub.add_parser(&amp;quot;show&amp;quot;, help=&amp;quot;selected headers only; never dumps the body&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--path&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning = sub.add_parser(&amp;quot;learn&amp;quot;, help=&amp;quot;explicitly train one reviewed event, then archive it&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;label&amp;quot;, choices=(&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;))&lt;br /&gt;
    learning.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--password-file&amp;quot;, default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
                          help=&amp;quot;root-only plaintext secret file; use --no-password only for an intentionally trusted local controller&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--no-password&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    rejecting = sub.add_parser(&amp;quot;reject&amp;quot;, help=&amp;quot;archive one event without training or deleting it&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;--reason&amp;quot;, default=&amp;quot;review rejected&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    try:&lt;br /&gt;
        require_root()&lt;br /&gt;
        root = Path(args.queue_root)&lt;br /&gt;
        if not root.is_absolute():&lt;br /&gt;
            raise FeedbackError(&amp;quot;queue root must be absolute&amp;quot;)&lt;br /&gt;
        check_root_directory(root)&lt;br /&gt;
        warn_processed_storage(root)&lt;br /&gt;
        if args.command == &amp;quot;list&amp;quot;:&lt;br /&gt;
            if not 1 &amp;lt;= args.limit &amp;lt;= 10000:&lt;br /&gt;
                raise FeedbackError(&amp;quot;limit must be between 1 and 10000&amp;quot;)&lt;br /&gt;
            list_events(root, args.state, args.limit)&lt;br /&gt;
        elif args.command == &amp;quot;show&amp;quot;:&lt;br /&gt;
            show_event(root, args.id, args.state, args.path)&lt;br /&gt;
        elif args.command == &amp;quot;reject&amp;quot;:&lt;br /&gt;
            emit(reject_event(root, args.id, args.reason))&lt;br /&gt;
        else:&lt;br /&gt;
            if not 0 &amp;lt; args.timeout &amp;lt;= 300:&lt;br /&gt;
                raise FeedbackError(&amp;quot;timeout must be between 0 and 300 seconds&amp;quot;)&lt;br /&gt;
            result = learn_event(root, args.id, args.label, executable=args.rspamc_bin,&lt;br /&gt;
                                 controller=args.controller, password_file=None if args.no_password else args.password_file,&lt;br /&gt;
                                 timeout=args.timeout)&lt;br /&gt;
            emit(result)&lt;br /&gt;
            return 0 if result[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot; else 2&lt;br /&gt;
        return 0&lt;br /&gt;
    except (FeedbackError, OSError, ValueError, TypeError):&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: operation refused or failed; inspect configuration/event locally&amp;quot;, file=sys.stderr)&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    raise SystemExit(main())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only background learning from the bounded IMAPSieve feedback queue.&lt;br /&gt;
&lt;br /&gt;
The collector remains a local spool writer. This process owns controller&lt;br /&gt;
credentials, learns only validated INBOX/Junk events, preserves every original,&lt;br /&gt;
and never retries a training operation with an uncertain result. Run foreground&lt;br /&gt;
under FreeBSD daemon(8), or use --once for one bounded batch.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import signal&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
import threading&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
_spec = importlib.util.spec_from_file_location(&amp;quot;feedback_worker_admin&amp;quot;, Path(__file__).with_name(&amp;quot;feedback-admin.py&amp;quot;))&lt;br /&gt;
admin = importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError = admin.FeedbackError&lt;br /&gt;
SHA_RE = re.compile(r&amp;quot;[0-9a-f]{64}\Z&amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS = 20000&lt;br /&gt;
UNCERTAIN = {&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learn_uncertain&amp;quot;, &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;quarantined_ordering&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def log_event(kind, **fields):&lt;br /&gt;
    # Callers pass only fixed diagnostics, numeric counts, event IDs and classes.&lt;br /&gt;
    print(json.dumps({&amp;quot;event&amp;quot;: kind, **fields}, ensure_ascii=True, sort_keys=True), flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _timestamp_ns(value):&lt;br /&gt;
    if not isinstance(value, str):&lt;br /&gt;
        raise ValueError(&amp;quot;missing timestamp&amp;quot;)&lt;br /&gt;
    parsed = datetime.fromisoformat(value.replace(&amp;quot;Z&amp;quot;, &amp;quot;+00:00&amp;quot;))&lt;br /&gt;
    if parsed.tzinfo is None:&lt;br /&gt;
        raise ValueError(&amp;quot;timestamp must have timezone&amp;quot;)&lt;br /&gt;
    return int(parsed.timestamp() * 1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def order_events(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read bounded metadata, ordered by collector created_ns, then event ID.&lt;br /&gt;
&lt;br /&gt;
    Legacy second-resolution timestamps are permitted only when the same&lt;br /&gt;
    content has no contradictory labels at an identical timestamp. Such a tie&lt;br /&gt;
    cannot be resolved from random event IDs and is quarantined without learning.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    pending = Path(root) / &amp;quot;pending&amp;quot;&lt;br /&gt;
    rows = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    with admin.Directory(pending):&lt;br /&gt;
        with os.scandir(pending) as entries:&lt;br /&gt;
            for entry in entries:&lt;br /&gt;
                if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                count += 1&lt;br /&gt;
                if count &amp;gt; admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
                    raise FeedbackError(&amp;quot;pending queue scan bound exceeded&amp;quot;)&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(pending / entry.name) as directory:&lt;br /&gt;
                        metadata = json.loads(directory.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    if not isinstance(metadata, dict):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid metadata&amp;quot;)&lt;br /&gt;
                    metadata = dict(metadata)&lt;br /&gt;
                    if metadata.get(&amp;quot;id&amp;quot;) != entry.name or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid event identity&amp;quot;)&lt;br /&gt;
                    if not isinstance(metadata.get(&amp;quot;sha256&amp;quot;), str) or not SHA_RE.fullmatch(metadata[&amp;quot;sha256&amp;quot;]):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid digest&amp;quot;)&lt;br /&gt;
                    admin.collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                                                   metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
                    timestamp = metadata.get(&amp;quot;created_ns&amp;quot;)&lt;br /&gt;
                    if isinstance(timestamp, int) and not isinstance(timestamp, bool) and timestamp &amp;gt; 0:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = timestamp&lt;br /&gt;
                    elif &amp;quot;created_ns&amp;quot; not in metadata:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = _timestamp_ns(metadata.get(&amp;quot;created_utc&amp;quot;))&lt;br /&gt;
                    else:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = 0&lt;br /&gt;
                        metadata[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
                    rows.append(metadata)&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    # A manual administrator may have archived this candidate.&lt;br /&gt;
                    continue&lt;br /&gt;
                except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                    rows.append({&amp;quot;id&amp;quot;: entry.name, &amp;quot;_invalid&amp;quot;: True, &amp;quot;_order_ns&amp;quot;: 0})&lt;br /&gt;
    tied = {}&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
            tied.setdefault((row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;]), set()).add(row[&amp;quot;label&amp;quot;])&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;) and len(tied[(row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;])]) &amp;gt; 1:&lt;br /&gt;
            row[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
    return sorted(rows, key=lambda row: (row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;id&amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _journal(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read root-owned receipts; successful classes use durable worker sequence.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    processed = Path(root) / &amp;quot;processed&amp;quot;&lt;br /&gt;
    latest, blocked = {}, set()&lt;br /&gt;
    max_sequence = used_bytes = count = 0&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
    admin.check_root_directory(processed)&lt;br /&gt;
    with os.scandir(processed) as entries:&lt;br /&gt;
        for entry in entries:&lt;br /&gt;
            if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                continue&lt;br /&gt;
            count += 1&lt;br /&gt;
            if count &amp;gt; MAX_JOURNAL_EVENTS:&lt;br /&gt;
                raise FeedbackError(&amp;quot;processed journal scan bound exceeded&amp;quot;)&lt;br /&gt;
            directory = processed / entry.name&lt;br /&gt;
            receipt = admin.receipt_for(directory)&lt;br /&gt;
            if not isinstance(receipt, dict) or receipt.get(&amp;quot;id&amp;quot;) != entry.name:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed receipt&amp;quot;)&lt;br /&gt;
            digest = receipt.get(&amp;quot;sha256&amp;quot;)&lt;br /&gt;
            if not isinstance(digest, str) or not SHA_RE.fullmatch(digest):&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed digest&amp;quot;)&lt;br /&gt;
            sequence = receipt.get(&amp;quot;worker_sequence&amp;quot;, 0)&lt;br /&gt;
            if not isinstance(sequence, int) or isinstance(sequence, bool) or sequence &amp;lt; 0:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed sequence&amp;quot;)&lt;br /&gt;
            max_sequence = max(max_sequence, sequence)&lt;br /&gt;
            status = receipt.get(&amp;quot;status&amp;quot;)&lt;br /&gt;
            if status in UNCERTAIN:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
            if status == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                label = receipt.get(&amp;quot;selected_class&amp;quot;)&lt;br /&gt;
                if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
                    raise FeedbackError(&amp;quot;invalid learned class&amp;quot;)&lt;br /&gt;
                # Existing manual receipts predate automatic processing; the old&lt;br /&gt;
                # admin refuses further training of a SHA once it has a receipt.&lt;br /&gt;
                fallback = _timestamp_ns(receipt.get(&amp;quot;finished_utc&amp;quot;, receipt.get(&amp;quot;reviewed_utc&amp;quot;)))&lt;br /&gt;
                position = (bool(sequence), sequence if sequence else fallback, entry.name)&lt;br /&gt;
                if digest not in latest or position &amp;gt; latest[digest][0]:&lt;br /&gt;
                    latest[digest] = (position, label)&lt;br /&gt;
            # Count snapshots and preserved originals, never recursively walk&lt;br /&gt;
            # arbitrary files in the mail-user-owned source/ directory.&lt;br /&gt;
            for part in (directory, directory / &amp;quot;source&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(part) as safe:&lt;br /&gt;
                        for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                            try:&lt;br /&gt;
                                if safe.fd is not None:&lt;br /&gt;
                                    info = os.stat(name, dir_fd=safe.fd, follow_symlinks=False)&lt;br /&gt;
                                else:&lt;br /&gt;
                                    info = (part / name).lstat()&lt;br /&gt;
                                if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                                    used_bytes += info.st_size&lt;br /&gt;
                            except FileNotFoundError:&lt;br /&gt;
                                continue&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    continue&lt;br /&gt;
    return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _check_trusted_parent_chain(path):&lt;br /&gt;
    if os.name != &amp;quot;posix&amp;quot;:&lt;br /&gt;
        return&lt;br /&gt;
    for parent in Path(path).parents:&lt;br /&gt;
        link_info = parent.lstat()&lt;br /&gt;
        info = parent.stat()&lt;br /&gt;
        if (link_info.st_uid != 0 or not stat.S_ISDIR(info.st_mode)&lt;br /&gt;
                or info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;executable parent directories must be controlled by root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def resolve_executable(executable):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Allow FreeBSD package symlinks, but pin both operations to their target.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    source = Path(executable)&lt;br /&gt;
    if not source.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be absolute&amp;quot;)&lt;br /&gt;
    source_info = source.lstat()&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and source_info.st_uid != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc entry must be owned by root&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(source)&lt;br /&gt;
    target = source.resolve(strict=True)&lt;br /&gt;
    info = target.lstat()&lt;br /&gt;
    if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be a regular file&amp;quot;)&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be root-owned and not writable by mail users&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(target)&lt;br /&gt;
    return str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def controller_ready(executable, controller, password_file, timeout):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Authenticated read-only controller probe; no candidate is archived yet.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    command = [resolve_executable(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;unsafe controller password file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;controller password file must be root-owned mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;stat&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        return subprocess.run(command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,&lt;br /&gt;
                              stderr=subprocess.DEVNULL, check=False, timeout=min(timeout, 5)).returncode == 0&lt;br /&gt;
    except (subprocess.TimeoutExpired, OSError):&lt;br /&gt;
        return False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def process_pending(root, *, executable=&amp;quot;/usr/local/bin/rspamc&amp;quot;, controller=&amp;quot;127.0.0.1:11334&amp;quot;,&lt;br /&gt;
                    password_file=admin.DEFAULT_PASSWORD_FILE, timeout=30, batch_size=20,&lt;br /&gt;
                    preflight=None, learner=None, stop_event=None):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Process at most batch_size candidates. Inject probes/learner for unit tests.&lt;br /&gt;
&lt;br /&gt;
    One admin lock serializes manual and background operations. A crash after a&lt;br /&gt;
    durable learning intent leaves status=learning, which blocks this SHA on the&lt;br /&gt;
    next pass; subsequent candidates are preserved as quarantined_uncertain.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    root = Path(root)&lt;br /&gt;
    preflight = controller_ready if preflight is None else preflight&lt;br /&gt;
    if learner is None:&lt;br /&gt;
        # FreeBSD pkg installs rspamc as a root-owned versioned symlink. Resolve&lt;br /&gt;
        # it once so preflight and training invoke the same trusted executable.&lt;br /&gt;
        executable = resolve_executable(executable)&lt;br /&gt;
        learner = admin.run_learning&lt;br /&gt;
    summary = {&amp;quot;learned&amp;quot;: 0, &amp;quot;duplicates&amp;quot;: 0, &amp;quot;quarantined&amp;quot;: 0, &amp;quot;invalid&amp;quot;: 0,&lt;br /&gt;
               &amp;quot;pending&amp;quot;: 0, &amp;quot;controller_unavailable&amp;quot;: False, &amp;quot;storage_blocked&amp;quot;: False,&lt;br /&gt;
               &amp;quot;archive_warning&amp;quot;: False}&lt;br /&gt;
    with admin.admin_lock(root):&lt;br /&gt;
        events = order_events(root)&lt;br /&gt;
        summary[&amp;quot;pending&amp;quot;] = len(events)&lt;br /&gt;
        if not events:&lt;br /&gt;
            return summary&lt;br /&gt;
        latest, blocked, sequence, used_bytes, archive_count = _journal(root)&lt;br /&gt;
        summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
        if used_bytes &amp;gt;= admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
            summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        if not preflight(executable, controller, password_file, timeout):&lt;br /&gt;
            summary[&amp;quot;controller_unavailable&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        handled = 0&lt;br /&gt;
        for candidate in events:&lt;br /&gt;
            if handled &amp;gt;= batch_size or (stop_event is not None and stop_event.is_set()):&lt;br /&gt;
                break&lt;br /&gt;
            if candidate.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            event_id = candidate[&amp;quot;id&amp;quot;]&lt;br /&gt;
            if (root / &amp;quot;processed&amp;quot; / event_id).exists():&lt;br /&gt;
                # Crash before prepare_archive moved pending/ID: its root-owned&lt;br /&gt;
                # intent is authoritative. Preserve the pending original for&lt;br /&gt;
                # manual recovery and continue independent content, never retry.&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            try:&lt;br /&gt;
                metadata, message = admin.read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
            except FileNotFoundError:&lt;br /&gt;
                continue&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            # Do not act if the queued ordering/identity changed since enumeration.&lt;br /&gt;
            if any(metadata.get(key) != candidate.get(key) for key in&lt;br /&gt;
                   (&amp;quot;sha256&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;created_ns&amp;quot;, &amp;quot;created_utc&amp;quot;)):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            if used_bytes + 2 * len(message) + 32768 &amp;gt; admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            label, digest = metadata[&amp;quot;label&amp;quot;], metadata[&amp;quot;sha256&amp;quot;]&lt;br /&gt;
            if candidate.get(&amp;quot;_ordering_invalid&amp;quot;):&lt;br /&gt;
                status, reason = &amp;quot;quarantined_ordering&amp;quot;, &amp;quot;event ordering is ambiguous&amp;quot;&lt;br /&gt;
            elif digest in blocked:&lt;br /&gt;
                status, reason = &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;a prior learning operation has an uncertain result&amp;quot;&lt;br /&gt;
            elif digest in latest and latest[digest][1] == label:&lt;br /&gt;
                status, reason = &amp;quot;duplicate&amp;quot;, &amp;quot;same class as the latest successful learning operation&amp;quot;&lt;br /&gt;
            else:&lt;br /&gt;
                status, reason = &amp;quot;learning&amp;quot;, &amp;quot;automatic explicit IMAP feedback&amp;quot;&lt;br /&gt;
            try:&lt;br /&gt;
                archive, receipt = admin.prepare_archive(root, event_id, metadata, message, status, label, reason)&lt;br /&gt;
            except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                # prepare_archive writes a root journal before moving the source;&lt;br /&gt;
                # stop instead of retrying an incompletely prepared operation.&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            sequence += 1&lt;br /&gt;
            receipt.update({&amp;quot;mode&amp;quot;: &amp;quot;automatic_imap_feedback&amp;quot;, &amp;quot;worker_sequence&amp;quot;: sequence,&lt;br /&gt;
                            &amp;quot;processed_ns&amp;quot;: time.time_ns(), &amp;quot;source_created_ns&amp;quot;: candidate[&amp;quot;_order_ns&amp;quot;]})&lt;br /&gt;
            if digest in latest:&lt;br /&gt;
                receipt[&amp;quot;previous_successful_class&amp;quot;] = latest[digest][1]&lt;br /&gt;
            # Durable intent exists before the only call that may change Bayes.&lt;br /&gt;
            admin.update_receipt(archive, receipt)&lt;br /&gt;
            if status == &amp;quot;learning&amp;quot;:&lt;br /&gt;
                try:&lt;br /&gt;
                    returncode = learner(message, label, executable=executable, controller=controller,&lt;br /&gt;
                                         password_file=password_file, timeout=timeout)&lt;br /&gt;
                except (subprocess.TimeoutExpired, OSError, FeedbackError):&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    receipt[&amp;quot;error&amp;quot;] = &amp;quot;client failure; server may have learned; no automatic retry&amp;quot;&lt;br /&gt;
                else:&lt;br /&gt;
                    receipt[&amp;quot;returncode&amp;quot;] = returncode&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if returncode == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    if returncode != 0:&lt;br /&gt;
                        receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; no automatic retry&amp;quot;&lt;br /&gt;
                receipt[&amp;quot;finished_utc&amp;quot;] = admin.collector.utc_now()&lt;br /&gt;
                admin.update_receipt(archive, receipt)&lt;br /&gt;
                if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                    latest[digest] = ((True, sequence, event_id), label)&lt;br /&gt;
                    summary[&amp;quot;learned&amp;quot;] += 1&lt;br /&gt;
                else:&lt;br /&gt;
                    blocked.add(digest)&lt;br /&gt;
                    summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            elif status == &amp;quot;duplicate&amp;quot;:&lt;br /&gt;
                summary[&amp;quot;duplicates&amp;quot;] += 1&lt;br /&gt;
            else:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
                summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            handled += 1&lt;br /&gt;
            used_bytes += 2 * len(message) + 32768&lt;br /&gt;
            archive_count += 1&lt;br /&gt;
            summary[&amp;quot;pending&amp;quot;] -= 1&lt;br /&gt;
            summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
            log_event(&amp;quot;feedback_processed&amp;quot;, id=event_id, label=label, status=receipt[&amp;quot;status&amp;quot;])&lt;br /&gt;
            if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learn_uncertain&amp;quot;:&lt;br /&gt;
                # The controller may have failed after the preflight. Leave all&lt;br /&gt;
                # other candidates pending until a fresh read-only probe passes.&lt;br /&gt;
                break&lt;br /&gt;
    return summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=admin.DEFAULT_ROOT)&lt;br /&gt;
    parser.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--password-file&amp;quot;, default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
    parser.add_argument(&amp;quot;--poll&amp;quot;, type=float, default=2)&lt;br /&gt;
    parser.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    parser.add_argument(&amp;quot;--batch-size&amp;quot;, type=int, default=20)&lt;br /&gt;
    parser.add_argument(&amp;quot;--once&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    admin.require_root()&lt;br /&gt;
    root = Path(args.queue_root)&lt;br /&gt;
    if not root.is_absolute() or not 0.2 &amp;lt;= args.poll &amp;lt;= 60 or not 0 &amp;lt; args.timeout &amp;lt;= 300 or not 1 &amp;lt;= args.batch_size &amp;lt;= 100:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid worker options&amp;quot;)&lt;br /&gt;
    admin.check_root_directory(root)&lt;br /&gt;
    stopping = threading.Event()&lt;br /&gt;
    for signum in (signal.SIGTERM, signal.SIGINT):&lt;br /&gt;
        signal.signal(signum, lambda *_: stopping.set())&lt;br /&gt;
    lockfd = os.open(root / &amp;quot;.worker.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)&lt;br /&gt;
        previous_state = None&lt;br /&gt;
        while not stopping.is_set():&lt;br /&gt;
            try:&lt;br /&gt;
                summary = process_pending(root, executable=args.rspamc_bin, controller=args.controller,&lt;br /&gt;
                                          password_file=args.password_file, timeout=args.timeout,&lt;br /&gt;
                                          batch_size=args.batch_size, stop_event=stopping)&lt;br /&gt;
                state = {key: value for key, value in summary.items() if key not in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)}&lt;br /&gt;
                if state != previous_state or any(summary[key] for key in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)):&lt;br /&gt;
                    log_event(&amp;quot;worker_state&amp;quot;, **summary)&lt;br /&gt;
                previous_state = state&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 2 if summary[&amp;quot;controller_unavailable&amp;quot;] or summary[&amp;quot;storage_blocked&amp;quot;] else 0&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                if previous_state != &amp;quot;local_error&amp;quot;:&lt;br /&gt;
                    log_event(&amp;quot;worker_error&amp;quot;, reason=&amp;quot;local queue, journal or configuration failure; no automatic retry of uncertain training&amp;quot;)&lt;br /&gt;
                previous_state = &amp;quot;local_error&amp;quot;&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 1&lt;br /&gt;
            stopping.wait(args.poll)&lt;br /&gt;
        return 0&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    try:&lt;br /&gt;
        raise SystemExit(main())&lt;br /&gt;
    except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
        log_event(&amp;quot;worker_start_failed&amp;quot;, reason=&amp;quot;root, configuration or worker lock required&amp;quot;)&lt;br /&gt;
        raise SystemExit(1)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== 16. Перенос почты с Яндекса через imapsync ==&lt;br /&gt;
&lt;br /&gt;
Однократное копирование переносит письма из существующего ящика Яндекса в существующий ящик Dovecot. Пример ниже использует &amp;lt;code&amp;gt;source@example.org&amp;lt;/code&amp;gt; как источник и &amp;lt;code&amp;gt;target@example.net&amp;lt;/code&amp;gt; на &amp;lt;code&amp;gt;mail.example.net&amp;lt;/code&amp;gt; как назначение. Замените эти значения своими. Переключать MX обратно на Яндекс для такого переноса не требуется.&lt;br /&gt;
&lt;br /&gt;
Папки источника размещаются внутри отдельной папки &amp;lt;code&amp;gt;Yandex-source&amp;lt;/code&amp;gt;. Оригиналы на Яндексе и текущая почта назначения сохраняются. Команды выполняются последовательно в одной интерактивной root-сессии SSH; после переподключения параметры &amp;lt;code&amp;gt;set --&amp;lt;/code&amp;gt; нужно задать заново.&lt;br /&gt;
&lt;br /&gt;
=== 16.1. Доступ и пакеты ===&lt;br /&gt;
&lt;br /&gt;
Разрешите IMAP в настройках почтовых программ Яндекса; для организации Яндекс 360 может потребоваться также разрешение протокола в кабинете администратора. Для источника используйте пароль приложения с доступом к Почте, для назначения — пароль своего ящика Dovecot. Если Яндекс блокирует IMAP и разрешить его нельзя, imapsync не сможет прочитать письма.&lt;br /&gt;
&lt;br /&gt;
Проверьте, что &amp;lt;code&amp;gt;/opt&amp;lt;/code&amp;gt; смонтирован как UFS, целевой ящик уже существует и имя &amp;lt;code&amp;gt;mail.example.net&amp;lt;/code&amp;gt; соответствует действительному сертификату почтового сервера. Импорт выполняется в обычное хранилище IMAP; файловый каталог пользователей Exim/Dovecot менять не требуется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
sudo pkg install imapsync&lt;br /&gt;
sudo /bin/sh&lt;br /&gt;
umask 077&lt;br /&gt;
install -d -o root -g wheel -m 0700 /opt/mail/migration/yandex-source&lt;br /&gt;
install -d -o root -g wheel -m 0700 /opt/mail/migration/yandex-source/cache /opt/mail/migration/yandex-source/log&lt;br /&gt;
cd /opt/mail/migration/yandex-source&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 16.2. Ввести пароли без отображения ===&lt;br /&gt;
&lt;br /&gt;
Следующие команды запрашивают пароли в терминале и записывают их в новые файлы. Пароли не попадают в аргументы процессов, переменные окружения или историю shell. С установленной выше маской файлы имеют права &amp;lt;code&amp;gt;0600&amp;lt;/code&amp;gt;, каталог — &amp;lt;code&amp;gt;0700&amp;lt;/code&amp;gt;. Режим создания &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; отказывается перезаписывать существующие файлы; не запускайте команды повторно, если пароли уже введены.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
python3.12 -c &#039;import getpass, os, sys; assert os.geteuid() == 0 and sys.stdin.isatty(); p = getpass.getpass(&amp;quot;Пароль приложения Яндекса: &amp;quot;); assert p and not any(c in p for c in &amp;quot;\x00\r\n&amp;quot;); f = open(&amp;quot;source.pass&amp;quot;, &amp;quot;x&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;); f.write(p + &amp;quot;\n&amp;quot;); f.close()&#039;&lt;br /&gt;
python3.12 -c &#039;import getpass, os, sys; assert os.geteuid() == 0 and sys.stdin.isatty(); p = getpass.getpass(&amp;quot;Пароль целевого ящика: &amp;quot;); assert p and not any(c in p for c in &amp;quot;\x00\r\n&amp;quot;); f = open(&amp;quot;target.pass&amp;quot;, &amp;quot;x&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;); f.write(p + &amp;quot;\n&amp;quot;); f.close()&#039;&lt;br /&gt;
stat -f &#039;%Su:%Sg %Lp %N&#039; source.pass target.pass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 16.3. Задать параметры и проверить план ===&lt;br /&gt;
&lt;br /&gt;
В примере включена проверка цепочки сертификатов и имён обоих серверов, разрешён TLS 1.2 и новее. Даты доставки и флаги новых писем копируются; повторный запуск не перезаписывает флаги уже перенесённых сообщений. &amp;lt;code&amp;gt;--syncduplicates&amp;lt;/code&amp;gt; сохраняет также повторяющиеся экземпляры писем источника. Не меняйте способ сопоставления сообщений между запусками одного переноса.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
set -- \&lt;br /&gt;
  --host1 imap.yandex.ru --port1 993 --ssl1 \&lt;br /&gt;
  --user1 source@example.org --passfile1 /opt/mail/migration/yandex-source/source.pass \&lt;br /&gt;
  --sslargs1 SSL_verify_mode=1 \&lt;br /&gt;
  --sslargs1 SSL_verifycn_scheme=imap \&lt;br /&gt;
  --sslargs1 SSL_verifycn_name=imap.yandex.ru \&lt;br /&gt;
  --sslargs1 SSL_hostname=imap.yandex.ru \&lt;br /&gt;
  --sslargs1 SSL_ca_file=/etc/ssl/cert.pem \&lt;br /&gt;
  --sslargs1 SSL_version=SSLv23:!SSLv3:!SSLv2:!TLSv1:!TLSv1_1 \&lt;br /&gt;
  --host2 mail.example.net --port2 993 --ssl2 \&lt;br /&gt;
  --user2 target@example.net --passfile2 /opt/mail/migration/yandex-source/target.pass \&lt;br /&gt;
  --sslargs2 SSL_verify_mode=1 \&lt;br /&gt;
  --sslargs2 SSL_verifycn_scheme=imap \&lt;br /&gt;
  --sslargs2 SSL_verifycn_name=mail.example.net \&lt;br /&gt;
  --sslargs2 SSL_hostname=mail.example.net \&lt;br /&gt;
  --sslargs2 SSL_ca_file=/etc/ssl/cert.pem \&lt;br /&gt;
  --sslargs2 SSL_version=SSLv23:!SSLv3:!SSLv2:!TLSv1:!TLSv1_1 \&lt;br /&gt;
  --subfolder2 Yandex-source \&lt;br /&gt;
  --useheader Message-Id --syncduplicates --syncinternaldates --noresyncflags \&lt;br /&gt;
  --noexpunge1 --noexpunge2 --nouidexpunge2 \&lt;br /&gt;
  --noemailreport1 --noemailreport2 --noreleasecheck \&lt;br /&gt;
  --tmpdir /opt/mail/migration/yandex-source/cache \&lt;br /&gt;
  --logdir /opt/mail/migration/yandex-source/log \&lt;br /&gt;
  --pidfile /opt/mail/migration/yandex-source/imapsync.pid --pidfilelocking&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверки ниже не копируют письма. Первая должна подтвердить вход в оба ящика; следующая показывает соответствие папок, затем выводятся количество и объём писем. После любой ошибки сначала устраните её и повторите проверку, прежде чем продолжать.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --justlogin --logfile login.log&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --dry --justfolders --logfile folders.log&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --dry --justfoldersizes --logfile sizes.log&lt;br /&gt;
df -h /opt&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сопоставьте суммарный объём источника из &amp;lt;code&amp;gt;sizes.log&amp;lt;/code&amp;gt; со свободным местом &amp;lt;code&amp;gt;df&amp;lt;/code&amp;gt;. Для первого полного копирования оставьте запас не менее 1 ГиБ сверх объёма переносимых писем, а для большого числа маленьких писем — дополнительное место под файловые метаданные. Если места недостаточно, не начинайте копирование: сначала увеличьте раздел или уменьшите согласованный объём переноса. В выводе плана все создаваемые папки должны находиться под &amp;lt;code&amp;gt;Yandex-source&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== 16.4. Скопировать и проверить результат ===&lt;br /&gt;
&lt;br /&gt;
Во время ручного запуска держите SSH-сессию открытой. Не запускайте два переноса этой пары одновременно; файл PID дополнительно защищает от параллельного старта. Не добавляйте параметры удаления &amp;lt;code&amp;gt;--delete1&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;--delete2&amp;lt;/code&amp;gt; или удаления папок. Включённые &amp;lt;code&amp;gt;--noexpunge*&amp;lt;/code&amp;gt; запрещают очистку удалённых сообщений.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --logfile copy.log&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После успешного завершения выполните повторную проверку с теми же параметрами:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --dry --logfile verify.log&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверьте код завершения &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, отсутствие ошибок в итогах imapsync и отсутствие новых писем, предлагаемых к копированию в проверочном запуске. Если источник получил новые сообщения во время переноса, повторите обычное копирование с теми же параметрами и затем снова проверку. Откройте папку &amp;lt;code&amp;gt;Yandex-source&amp;lt;/code&amp;gt; и её подпапки в SnappyMail; выборочно сравните письма, вложения, даты и состояние прочитано/непрочитано с источником.&lt;br /&gt;
&lt;br /&gt;
Если перенос прервался, журналы и cache остаются в защищённом каталоге. После устранения причины повторите команду с прежними параметрами и cache. Перед перезапуском убедитесь, что предыдущий процесс завершён; наличие старого PID-файла само по себе не доказывает, что процесс ещё работает.&lt;br /&gt;
&lt;br /&gt;
После окончательной проверки и завершения всех процессов удалите временные файлы паролей:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
rm -f -- /opt/mail/migration/yandex-source/source.pass /opt/mail/migration/yandex-source/target.pass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Затем отзовите использованный пароль приложения Яндекса, если он был создан только для миграции. Сохранённые журналы доступны root и могут содержать адреса и названия папок; не публикуйте их без обезличивания.&lt;br /&gt;
&lt;br /&gt;
=== 16.5. Ошибки доступа ===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Invalid credentials or IMAP is disabled&amp;lt;/code&amp;gt;: проверьте полный адрес источника, пароль приложения и разрешение IMAP у пользователя и организации. Обычный пароль аккаунта может не подходить для почтового клиента.&lt;br /&gt;
* &amp;lt;code&amp;gt;To use Yandex Mail in other email clients, you need a Yandex 360 for Business plan&amp;lt;/code&amp;gt;: доступ к IMAP ограничен условиями тарифа Яндекса. Сначала восстановите доступ на стороне Яндекса; изменение параметров Dovecot эту причину не устраняет.&lt;br /&gt;
* Ошибка сертификата: исправьте имя сервера, время или цепочку сертификатов. Не отключайте &amp;lt;code&amp;gt;SSL_verify_mode&amp;lt;/code&amp;gt; и проверку имени.&lt;br /&gt;
* Недостаточно места: остановите перенос, освободите или увеличьте хранилище и повторите с прежними параметрами. Не удаляйте исходные письма ради освобождения места на назначении.&lt;br /&gt;
&lt;br /&gt;
Источники: [https://imapsync.lamiral.info/README руководство imapsync], [https://imapsync.lamiral.info/FAQ.d/FAQ.Yandex.txt параметры Яндекса], [https://imapsync.lamiral.info/FAQ.d/FAQ.Duplicates.txt дубликаты], [https://yandex.ru/support/yandex-360/business/admin/ru/mail/email-clients/ разрешение почтовых протоколов].&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=692</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=692"/>
		<updated>2026-10-30T01:32:59Z</updated>

		<summary type="html">&lt;p&gt;Bas: Добавлен раздел о копировании почты Яндекса через imapsync с проверкой TLS и безопасным вводом паролей.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет → TCP 25 / STARTTLS → Exim&lt;br /&gt;
Клиент   → TCP 465 TLS или 587 STARTTLS + AUTH → Exim&lt;br /&gt;
Exim     → LMTP Unix socket → Dovecot → /opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim     → AUTH Unix socket → Dovecot → файловый каталог пользователей&lt;br /&gt;
Клиент   → TCP 993 TLS → Dovecot&lt;br /&gt;
SnappyMail (необязательно) → те же IMAPS и SMTP AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
sysrc hostname=mail.example.org&lt;br /&gt;
hostname mail.example.org&lt;br /&gt;
sysrc defaultrouter=198.51.100.1&lt;br /&gt;
# Только если в работающей таблице отсутствует default route:&lt;br /&gt;
route add default 198.51.100.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo /dev/da1 | awk &#039;{print $3}&#039;)&lt;br /&gt;
test -n &amp;quot;$disk_bytes&amp;quot; &amp;amp;&amp;amp; test &amp;quot;$disk_bytes&amp;quot; -gt 0 || exit 1&lt;br /&gt;
cmp -s -n &amp;quot;$disk_bytes&amp;quot; /dev/da1 /dev/zero&lt;br /&gt;
printf &#039;cmp status: %s\n&#039; &amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
pkg update&lt;br /&gt;
pkg install -n exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
# После проверки плана:&lt;br /&gt;
pkg install -y exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
sysrc exim_enable=NO dovecot_enable=NO&lt;br /&gt;
sysrc sendmail_enable=NONE sendmail_submit_enable=NO&lt;br /&gt;
sysrc sendmail_outbound_enable=NO sendmail_msp_queue_enable=NO&lt;br /&gt;
service sendmail onestop&lt;br /&gt;
/usr/local/sbin/exim -bV&lt;br /&gt;
/usr/local/sbin/dovecot --version&lt;br /&gt;
pw usershow mailnull&lt;br /&gt;
pw groupshow mail&lt;br /&gt;
pw groupshow dovecot&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only review and explicit training of IMAPSieve feedback.&lt;br /&gt;
&lt;br /&gt;
No command trains automatically. list/show are local reads. learn ID spam|ham&lt;br /&gt;
is the only command that invokes rspamc. reject ID archives without training.&lt;br /&gt;
Queue root and processed must be root-owned, non-writable by mail users;&lt;br /&gt;
pending is separately owned by vmail, mode 0700. Archived originals remain in&lt;br /&gt;
processed/ID/source; only root-owned snapshots are used for training or review.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from contextlib import contextmanager&lt;br /&gt;
from email import policy&lt;br /&gt;
from email.parser import BytesHeaderParser&lt;br /&gt;
import hashlib&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import shutil&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
&lt;br /&gt;
spec = importlib.util.spec_from_file_location(&amp;quot;feedback_collector&amp;quot;, Path(__file__).with_name(&amp;quot;feedback.py&amp;quot;))&lt;br /&gt;
collector = importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE = collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError = collector.FeedbackError&lt;br /&gt;
ID_RE = re.compile(r&amp;quot;[0-9a-f]{32}\Z&amp;quot;)&lt;br /&gt;
DEFAULT_ROOT = &amp;quot;/opt/mail/rspamd-feedback&amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE = &amp;quot;/usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
HEADERS = (&amp;quot;From&amp;quot;, &amp;quot;To&amp;quot;, &amp;quot;Date&amp;quot;, &amp;quot;Subject&amp;quot;, &amp;quot;Message-ID&amp;quot;, &amp;quot;X-Spam-Status&amp;quot;, &amp;quot;X-Spam-Score&amp;quot;, &amp;quot;X-Rspamd-Action&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def emit(value):&lt;br /&gt;
    print(json.dumps(value, ensure_ascii=True, sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def require_root():&lt;br /&gt;
    if not hasattr(os, &amp;quot;geteuid&amp;quot;) or os.geteuid() != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;this administrative command requires root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_id(event_id):&lt;br /&gt;
    if not ID_RE.fullmatch(event_id):&lt;br /&gt;
        raise FeedbackError(&amp;quot;ID must be exactly 32 lowercase hexadecimal characters&amp;quot;)&lt;br /&gt;
    return event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Directory:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Nofollow directory descriptor on FreeBSD; portable fallback for local tests.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, path):&lt;br /&gt;
        self.path = Path(path)&lt;br /&gt;
        info = self.path.lstat()&lt;br /&gt;
        if not stat.S_ISDIR(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;expected a real directory&amp;quot;)&lt;br /&gt;
        self.fd = None&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            self.fd = os.open(self.path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
    def __enter__(self):&lt;br /&gt;
        return self&lt;br /&gt;
&lt;br /&gt;
    def __exit__(self, *args):&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
    def read(self, name, limit):&lt;br /&gt;
        if &amp;quot;/&amp;quot; in name or &amp;quot;\\&amp;quot; in name or name in (&amp;quot;.&amp;quot;, &amp;quot;..&amp;quot;):&lt;br /&gt;
            raise FeedbackError(&amp;quot;invalid internal filename&amp;quot;)&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=self.fd)&lt;br /&gt;
        else:&lt;br /&gt;
            path = self.path / name&lt;br /&gt;
            if path.is_symlink():&lt;br /&gt;
                raise FeedbackError(&amp;quot;symlink is not an event file&amp;quot;)&lt;br /&gt;
            fd = os.open(path, os.O_RDONLY | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0))&lt;br /&gt;
        with os.fdopen(fd, &amp;quot;rb&amp;quot;) as source:&lt;br /&gt;
            info = os.fstat(source.fileno())&lt;br /&gt;
            if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_size &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file is not a bounded, single-link regular file&amp;quot;)&lt;br /&gt;
            result = source.read(limit + 1)&lt;br /&gt;
            if len(result) &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file exceeded the size limit&amp;quot;)&lt;br /&gt;
            return result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def read_event(directory, event_id):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    # On POSIX open the event relative to the already opened parent descriptor.&lt;br /&gt;
    # The parent path is deployment-owned, and a symlink event is rejected.&lt;br /&gt;
    with Directory(Path(directory) / event_id) as entry:&lt;br /&gt;
        metadata = json.loads(entry.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
        message = entry.read(&amp;quot;message.eml&amp;quot;, MAX_MESSAGE)&lt;br /&gt;
    if not isinstance(metadata, dict) or not isinstance(metadata.get(&amp;quot;user&amp;quot;), str):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                             metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
    if metadata.get(&amp;quot;id&amp;quot;) != event_id or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    if metadata.get(&amp;quot;bytes&amp;quot;) != len(message) or metadata.get(&amp;quot;sha256&amp;quot;) != hashlib.sha256(message).hexdigest():&lt;br /&gt;
        raise FeedbackError(&amp;quot;event content does not match its metadata&amp;quot;)&lt;br /&gt;
    return metadata, message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def write_private(path, value, *, json_value=False):&lt;br /&gt;
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0)&lt;br /&gt;
    fd = os.open(path, flags, 0o600)&lt;br /&gt;
    with os.fdopen(fd, &amp;quot;wb&amp;quot;) as output:&lt;br /&gt;
        if json_value:&lt;br /&gt;
            value = (json.dumps(value, ensure_ascii=True, sort_keys=True) + &amp;quot;\n&amp;quot;).encode()&lt;br /&gt;
        output.write(value)&lt;br /&gt;
        output.flush()&lt;br /&gt;
        os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def update_receipt(directory, receipt):&lt;br /&gt;
    temporary = directory / &amp;quot;.admin-new.json&amp;quot;&lt;br /&gt;
    write_private(temporary, receipt, json_value=True)&lt;br /&gt;
    os.replace(temporary, directory / &amp;quot;admin.json&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_root_directory(path):&lt;br /&gt;
    with Directory(path):&lt;br /&gt;
        info = Path(path).stat()&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;administrative directories must be root-owned and not group/other writable&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def admin_lock(root):&lt;br /&gt;
    check_root_directory(root)&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        processed.mkdir(mode=0o700)&lt;br /&gt;
    check_root_directory(processed)&lt;br /&gt;
    fd = os.open(root / &amp;quot;.admin.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(fd, fcntl.LOCK_EX)&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def receipt_for(directory):&lt;br /&gt;
    with Directory(directory) as entry:&lt;br /&gt;
        return json.loads(entry.read(&amp;quot;admin.json&amp;quot;, 16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def find_prior(root, sha256):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return None&lt;br /&gt;
    for entry in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
            continue&lt;br /&gt;
        try:&lt;br /&gt;
            receipt = receipt_for(entry)&lt;br /&gt;
        except (OSError, ValueError, FeedbackError):&lt;br /&gt;
            # A damaged journal must never silently allow repeated training.&lt;br /&gt;
            raise FeedbackError(&amp;quot;processed journal is incomplete; inspect it before more training&amp;quot;)&lt;br /&gt;
        if receipt.get(&amp;quot;sha256&amp;quot;) == sha256 and receipt.get(&amp;quot;status&amp;quot;) in (&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learned&amp;quot;, &amp;quot;learn_uncertain&amp;quot;):&lt;br /&gt;
            return receipt&lt;br /&gt;
    return None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def prepare_archive(root, event_id, metadata, message, status, label=None, reason=None):&lt;br /&gt;
    if shutil.disk_usage(root).free &amp;lt; collector.MIN_FREE_BYTES + len(message) + 32768:&lt;br /&gt;
        raise FeedbackError(&amp;quot;insufficient free disk space to preserve the reviewed snapshot&amp;quot;)&lt;br /&gt;
    archive = root / &amp;quot;processed&amp;quot; / event_id&lt;br /&gt;
    archive.mkdir(mode=0o700)&lt;br /&gt;
    receipt = {&amp;quot;schema&amp;quot;: 1, &amp;quot;id&amp;quot;: event_id, &amp;quot;sha256&amp;quot;: metadata[&amp;quot;sha256&amp;quot;],&lt;br /&gt;
               &amp;quot;reviewed_utc&amp;quot;: collector.utc_now(), &amp;quot;status&amp;quot;: &amp;quot;preparing&amp;quot;,&lt;br /&gt;
               &amp;quot;selected_class&amp;quot;: label, &amp;quot;reason&amp;quot;: reason}&lt;br /&gt;
    write_private(archive / &amp;quot;admin.json&amp;quot;, receipt, json_value=True)&lt;br /&gt;
    write_private(archive / &amp;quot;message.eml&amp;quot;, message)&lt;br /&gt;
    write_private(archive / &amp;quot;metadata.json&amp;quot;, json.dumps(metadata, ensure_ascii=True).encode())&lt;br /&gt;
    # Keep the complete original event. Never traverse or execute files in source/.&lt;br /&gt;
    os.rename(root / &amp;quot;pending&amp;quot; / event_id, archive / &amp;quot;source&amp;quot;)&lt;br /&gt;
    receipt[&amp;quot;status&amp;quot;] = status&lt;br /&gt;
    update_receipt(archive, receipt)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;pending&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;processed&amp;quot;)&lt;br /&gt;
    return archive, receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def run_learning(message, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    executable = Path(executable)&lt;br /&gt;
    if not executable.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be an absolute path&amp;quot;)&lt;br /&gt;
    command = [str(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be an absolute regular file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be root-owned, mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        # rspamc reads the file itself; the secret never appears in argv or logs.&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;learn_&amp;quot; + label)&lt;br /&gt;
    # No -u/-d: one shared Bayes classifier. No shell and no automatic retry.&lt;br /&gt;
    return subprocess.run(command, input=message, stdout=subprocess.DEVNULL,&lt;br /&gt;
                          stderr=subprocess.DEVNULL, timeout=timeout, check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def learn_event(root, event_id, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid training class&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        prior = find_prior(root, metadata[&amp;quot;sha256&amp;quot;])&lt;br /&gt;
        if prior:&lt;br /&gt;
            raise FeedbackError(&amp;quot;this content has a prior learned or uncertain event; inspect it and reject the duplicate&amp;quot;)&lt;br /&gt;
        archive, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;learning&amp;quot;, label)&lt;br /&gt;
        try:&lt;br /&gt;
            rc = run_learning(message, label, executable=executable, controller=controller,&lt;br /&gt;
                              password_file=password_file, timeout=timeout)&lt;br /&gt;
        except subprocess.TimeoutExpired:&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client timeout; server may have learned; do not automatically retry&amp;quot;&lt;br /&gt;
        except (OSError, FeedbackError):&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_failed&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client could not start or local configuration was invalid&amp;quot;&lt;br /&gt;
        else:&lt;br /&gt;
            receipt[&amp;quot;returncode&amp;quot;] = rc&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if rc == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            if rc:&lt;br /&gt;
                receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; inspect server before any retry&amp;quot;&lt;br /&gt;
        receipt[&amp;quot;finished_utc&amp;quot;] = collector.utc_now()&lt;br /&gt;
        update_receipt(archive, receipt)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def reject_event(root, event_id, reason=&amp;quot;review rejected&amp;quot;):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if len(reason) &amp;gt; 500:&lt;br /&gt;
        raise FeedbackError(&amp;quot;reason is too long&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        _, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;rejected&amp;quot;, reason=reason)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def list_events(root, state, limit):&lt;br /&gt;
    count = 0&lt;br /&gt;
    for name in ((&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;) if state == &amp;quot;all&amp;quot; else (state,)):&lt;br /&gt;
        directory = root / name&lt;br /&gt;
        if not directory.exists():&lt;br /&gt;
            continue&lt;br /&gt;
        with Directory(directory):&lt;br /&gt;
            for entry in directory.iterdir():&lt;br /&gt;
                if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                if count &amp;gt;= limit:&lt;br /&gt;
                    emit({&amp;quot;notice&amp;quot;: &amp;quot;limit reached&amp;quot;, &amp;quot;limit&amp;quot;: limit})&lt;br /&gt;
                    return&lt;br /&gt;
                try:&lt;br /&gt;
                    with Directory(entry) as item:&lt;br /&gt;
                        metadata = json.loads(item.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    row = {key: metadata.get(key) for key in (&amp;quot;id&amp;quot;, &amp;quot;created_utc&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;user&amp;quot;, &amp;quot;source&amp;quot;, &amp;quot;destination&amp;quot;, &amp;quot;bytes&amp;quot;)}&lt;br /&gt;
                    row[&amp;quot;state&amp;quot;] = name&lt;br /&gt;
                    if name == &amp;quot;processed&amp;quot;:&lt;br /&gt;
                        row[&amp;quot;review&amp;quot;] = receipt_for(entry)&lt;br /&gt;
                    emit(row)&lt;br /&gt;
                except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                    emit({&amp;quot;id&amp;quot;: entry.name, &amp;quot;state&amp;quot;: name, &amp;quot;error&amp;quot;: &amp;quot;invalid event; manual inspection required&amp;quot;})&lt;br /&gt;
                count += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def show_event(root, event_id, state, path_only):&lt;br /&gt;
    metadata, message = read_event(root / state, check_id(event_id))&lt;br /&gt;
    if path_only:&lt;br /&gt;
        emit({&amp;quot;id&amp;quot;: event_id, &amp;quot;message_path&amp;quot;: str(root / state / event_id / &amp;quot;message.eml&amp;quot;)})&lt;br /&gt;
        return&lt;br /&gt;
    headers = BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message), 262144)])&lt;br /&gt;
    emit({&amp;quot;metadata&amp;quot;: metadata, &amp;quot;headers&amp;quot;: {name: [str(v)[:2000] for v in headers.get_all(name, [])[:10]] for name in HEADERS}})&lt;br /&gt;
    if state == &amp;quot;processed&amp;quot;:&lt;br /&gt;
        emit({&amp;quot;review&amp;quot;: receipt_for(root / state / event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def warn_processed_storage(root):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return&lt;br /&gt;
    total = count = 0&lt;br /&gt;
    for event in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(event.name) or event.is_symlink() or not event.is_dir():&lt;br /&gt;
            continue&lt;br /&gt;
        count += 1&lt;br /&gt;
        for prefix in (event, event / &amp;quot;source&amp;quot;):&lt;br /&gt;
            if prefix.is_symlink():&lt;br /&gt;
                continue&lt;br /&gt;
            for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    info = (prefix / name).lstat()&lt;br /&gt;
                    if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                        total += info.st_size&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    pass&lt;br /&gt;
    if total &amp;gt;= collector.MAX_PENDING_BYTES or count &amp;gt;= collector.MAX_PENDING_EVENTS:&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: WARNING processed archive reached 512 MiB or 1024 events; review retention manually; nothing deleted&amp;quot;, file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=DEFAULT_ROOT)&lt;br /&gt;
    sub = parser.add_subparsers(dest=&amp;quot;command&amp;quot;, required=True)&lt;br /&gt;
    listing = sub.add_parser(&amp;quot;list&amp;quot;, help=&amp;quot;JSON lines, metadata only&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;, &amp;quot;all&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--limit&amp;quot;, type=int, default=100)&lt;br /&gt;
    showing = sub.add_parser(&amp;quot;show&amp;quot;, help=&amp;quot;selected headers only; never dumps the body&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--path&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning = sub.add_parser(&amp;quot;learn&amp;quot;, help=&amp;quot;explicitly train one reviewed event, then archive it&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;label&amp;quot;, choices=(&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;))&lt;br /&gt;
    learning.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--password-file&amp;quot;, default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
                          help=&amp;quot;root-only plaintext secret file; use --no-password only for an intentionally trusted local controller&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--no-password&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    rejecting = sub.add_parser(&amp;quot;reject&amp;quot;, help=&amp;quot;archive one event without training or deleting it&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;--reason&amp;quot;, default=&amp;quot;review rejected&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    try:&lt;br /&gt;
        require_root()&lt;br /&gt;
        root = Path(args.queue_root)&lt;br /&gt;
        if not root.is_absolute():&lt;br /&gt;
            raise FeedbackError(&amp;quot;queue root must be absolute&amp;quot;)&lt;br /&gt;
        check_root_directory(root)&lt;br /&gt;
        warn_processed_storage(root)&lt;br /&gt;
        if args.command == &amp;quot;list&amp;quot;:&lt;br /&gt;
            if not 1 &amp;lt;= args.limit &amp;lt;= 10000:&lt;br /&gt;
                raise FeedbackError(&amp;quot;limit must be between 1 and 10000&amp;quot;)&lt;br /&gt;
            list_events(root, args.state, args.limit)&lt;br /&gt;
        elif args.command == &amp;quot;show&amp;quot;:&lt;br /&gt;
            show_event(root, args.id, args.state, args.path)&lt;br /&gt;
        elif args.command == &amp;quot;reject&amp;quot;:&lt;br /&gt;
            emit(reject_event(root, args.id, args.reason))&lt;br /&gt;
        else:&lt;br /&gt;
            if not 0 &amp;lt; args.timeout &amp;lt;= 300:&lt;br /&gt;
                raise FeedbackError(&amp;quot;timeout must be between 0 and 300 seconds&amp;quot;)&lt;br /&gt;
            result = learn_event(root, args.id, args.label, executable=args.rspamc_bin,&lt;br /&gt;
                                 controller=args.controller, password_file=None if args.no_password else args.password_file,&lt;br /&gt;
                                 timeout=args.timeout)&lt;br /&gt;
            emit(result)&lt;br /&gt;
            return 0 if result[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot; else 2&lt;br /&gt;
        return 0&lt;br /&gt;
    except (FeedbackError, OSError, ValueError, TypeError):&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: operation refused or failed; inspect configuration/event locally&amp;quot;, file=sys.stderr)&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    raise SystemExit(main())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only background learning from the bounded IMAPSieve feedback queue.&lt;br /&gt;
&lt;br /&gt;
The collector remains a local spool writer. This process owns controller&lt;br /&gt;
credentials, learns only validated INBOX/Junk events, preserves every original,&lt;br /&gt;
and never retries a training operation with an uncertain result. Run foreground&lt;br /&gt;
under FreeBSD daemon(8), or use --once for one bounded batch.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import signal&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
import threading&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
_spec = importlib.util.spec_from_file_location(&amp;quot;feedback_worker_admin&amp;quot;, Path(__file__).with_name(&amp;quot;feedback-admin.py&amp;quot;))&lt;br /&gt;
admin = importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError = admin.FeedbackError&lt;br /&gt;
SHA_RE = re.compile(r&amp;quot;[0-9a-f]{64}\Z&amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS = 20000&lt;br /&gt;
UNCERTAIN = {&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learn_uncertain&amp;quot;, &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;quarantined_ordering&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def log_event(kind, **fields):&lt;br /&gt;
    # Callers pass only fixed diagnostics, numeric counts, event IDs and classes.&lt;br /&gt;
    print(json.dumps({&amp;quot;event&amp;quot;: kind, **fields}, ensure_ascii=True, sort_keys=True), flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _timestamp_ns(value):&lt;br /&gt;
    if not isinstance(value, str):&lt;br /&gt;
        raise ValueError(&amp;quot;missing timestamp&amp;quot;)&lt;br /&gt;
    parsed = datetime.fromisoformat(value.replace(&amp;quot;Z&amp;quot;, &amp;quot;+00:00&amp;quot;))&lt;br /&gt;
    if parsed.tzinfo is None:&lt;br /&gt;
        raise ValueError(&amp;quot;timestamp must have timezone&amp;quot;)&lt;br /&gt;
    return int(parsed.timestamp() * 1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def order_events(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read bounded metadata, ordered by collector created_ns, then event ID.&lt;br /&gt;
&lt;br /&gt;
    Legacy second-resolution timestamps are permitted only when the same&lt;br /&gt;
    content has no contradictory labels at an identical timestamp. Such a tie&lt;br /&gt;
    cannot be resolved from random event IDs and is quarantined without learning.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    pending = Path(root) / &amp;quot;pending&amp;quot;&lt;br /&gt;
    rows = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    with admin.Directory(pending):&lt;br /&gt;
        with os.scandir(pending) as entries:&lt;br /&gt;
            for entry in entries:&lt;br /&gt;
                if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                count += 1&lt;br /&gt;
                if count &amp;gt; admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
                    raise FeedbackError(&amp;quot;pending queue scan bound exceeded&amp;quot;)&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(pending / entry.name) as directory:&lt;br /&gt;
                        metadata = json.loads(directory.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    if not isinstance(metadata, dict):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid metadata&amp;quot;)&lt;br /&gt;
                    metadata = dict(metadata)&lt;br /&gt;
                    if metadata.get(&amp;quot;id&amp;quot;) != entry.name or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid event identity&amp;quot;)&lt;br /&gt;
                    if not isinstance(metadata.get(&amp;quot;sha256&amp;quot;), str) or not SHA_RE.fullmatch(metadata[&amp;quot;sha256&amp;quot;]):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid digest&amp;quot;)&lt;br /&gt;
                    admin.collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                                                   metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
                    timestamp = metadata.get(&amp;quot;created_ns&amp;quot;)&lt;br /&gt;
                    if isinstance(timestamp, int) and not isinstance(timestamp, bool) and timestamp &amp;gt; 0:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = timestamp&lt;br /&gt;
                    elif &amp;quot;created_ns&amp;quot; not in metadata:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = _timestamp_ns(metadata.get(&amp;quot;created_utc&amp;quot;))&lt;br /&gt;
                    else:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = 0&lt;br /&gt;
                        metadata[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
                    rows.append(metadata)&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    # A manual administrator may have archived this candidate.&lt;br /&gt;
                    continue&lt;br /&gt;
                except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                    rows.append({&amp;quot;id&amp;quot;: entry.name, &amp;quot;_invalid&amp;quot;: True, &amp;quot;_order_ns&amp;quot;: 0})&lt;br /&gt;
    tied = {}&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
            tied.setdefault((row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;]), set()).add(row[&amp;quot;label&amp;quot;])&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;) and len(tied[(row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;])]) &amp;gt; 1:&lt;br /&gt;
            row[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
    return sorted(rows, key=lambda row: (row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;id&amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _journal(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read root-owned receipts; successful classes use durable worker sequence.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    processed = Path(root) / &amp;quot;processed&amp;quot;&lt;br /&gt;
    latest, blocked = {}, set()&lt;br /&gt;
    max_sequence = used_bytes = count = 0&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
    admin.check_root_directory(processed)&lt;br /&gt;
    with os.scandir(processed) as entries:&lt;br /&gt;
        for entry in entries:&lt;br /&gt;
            if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                continue&lt;br /&gt;
            count += 1&lt;br /&gt;
            if count &amp;gt; MAX_JOURNAL_EVENTS:&lt;br /&gt;
                raise FeedbackError(&amp;quot;processed journal scan bound exceeded&amp;quot;)&lt;br /&gt;
            directory = processed / entry.name&lt;br /&gt;
            receipt = admin.receipt_for(directory)&lt;br /&gt;
            if not isinstance(receipt, dict) or receipt.get(&amp;quot;id&amp;quot;) != entry.name:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed receipt&amp;quot;)&lt;br /&gt;
            digest = receipt.get(&amp;quot;sha256&amp;quot;)&lt;br /&gt;
            if not isinstance(digest, str) or not SHA_RE.fullmatch(digest):&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed digest&amp;quot;)&lt;br /&gt;
            sequence = receipt.get(&amp;quot;worker_sequence&amp;quot;, 0)&lt;br /&gt;
            if not isinstance(sequence, int) or isinstance(sequence, bool) or sequence &amp;lt; 0:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed sequence&amp;quot;)&lt;br /&gt;
            max_sequence = max(max_sequence, sequence)&lt;br /&gt;
            status = receipt.get(&amp;quot;status&amp;quot;)&lt;br /&gt;
            if status in UNCERTAIN:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
            if status == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                label = receipt.get(&amp;quot;selected_class&amp;quot;)&lt;br /&gt;
                if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
                    raise FeedbackError(&amp;quot;invalid learned class&amp;quot;)&lt;br /&gt;
                # Existing manual receipts predate automatic processing; the old&lt;br /&gt;
                # admin refuses further training of a SHA once it has a receipt.&lt;br /&gt;
                fallback = _timestamp_ns(receipt.get(&amp;quot;finished_utc&amp;quot;, receipt.get(&amp;quot;reviewed_utc&amp;quot;)))&lt;br /&gt;
                position = (bool(sequence), sequence if sequence else fallback, entry.name)&lt;br /&gt;
                if digest not in latest or position &amp;gt; latest[digest][0]:&lt;br /&gt;
                    latest[digest] = (position, label)&lt;br /&gt;
            # Count snapshots and preserved originals, never recursively walk&lt;br /&gt;
            # arbitrary files in the mail-user-owned source/ directory.&lt;br /&gt;
            for part in (directory, directory / &amp;quot;source&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(part) as safe:&lt;br /&gt;
                        for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                            try:&lt;br /&gt;
                                if safe.fd is not None:&lt;br /&gt;
                                    info = os.stat(name, dir_fd=safe.fd, follow_symlinks=False)&lt;br /&gt;
                                else:&lt;br /&gt;
                                    info = (part / name).lstat()&lt;br /&gt;
                                if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                                    used_bytes += info.st_size&lt;br /&gt;
                            except FileNotFoundError:&lt;br /&gt;
                                continue&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    continue&lt;br /&gt;
    return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _check_trusted_parent_chain(path):&lt;br /&gt;
    if os.name != &amp;quot;posix&amp;quot;:&lt;br /&gt;
        return&lt;br /&gt;
    for parent in Path(path).parents:&lt;br /&gt;
        link_info = parent.lstat()&lt;br /&gt;
        info = parent.stat()&lt;br /&gt;
        if (link_info.st_uid != 0 or not stat.S_ISDIR(info.st_mode)&lt;br /&gt;
                or info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;executable parent directories must be controlled by root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def resolve_executable(executable):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Allow FreeBSD package symlinks, but pin both operations to their target.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    source = Path(executable)&lt;br /&gt;
    if not source.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be absolute&amp;quot;)&lt;br /&gt;
    source_info = source.lstat()&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and source_info.st_uid != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc entry must be owned by root&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(source)&lt;br /&gt;
    target = source.resolve(strict=True)&lt;br /&gt;
    info = target.lstat()&lt;br /&gt;
    if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be a regular file&amp;quot;)&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be root-owned and not writable by mail users&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(target)&lt;br /&gt;
    return str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def controller_ready(executable, controller, password_file, timeout):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Authenticated read-only controller probe; no candidate is archived yet.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    command = [resolve_executable(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;unsafe controller password file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;controller password file must be root-owned mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;stat&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        return subprocess.run(command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,&lt;br /&gt;
                              stderr=subprocess.DEVNULL, check=False, timeout=min(timeout, 5)).returncode == 0&lt;br /&gt;
    except (subprocess.TimeoutExpired, OSError):&lt;br /&gt;
        return False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def process_pending(root, *, executable=&amp;quot;/usr/local/bin/rspamc&amp;quot;, controller=&amp;quot;127.0.0.1:11334&amp;quot;,&lt;br /&gt;
                    password_file=admin.DEFAULT_PASSWORD_FILE, timeout=30, batch_size=20,&lt;br /&gt;
                    preflight=None, learner=None, stop_event=None):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Process at most batch_size candidates. Inject probes/learner for unit tests.&lt;br /&gt;
&lt;br /&gt;
    One admin lock serializes manual and background operations. A crash after a&lt;br /&gt;
    durable learning intent leaves status=learning, which blocks this SHA on the&lt;br /&gt;
    next pass; subsequent candidates are preserved as quarantined_uncertain.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    root = Path(root)&lt;br /&gt;
    preflight = controller_ready if preflight is None else preflight&lt;br /&gt;
    if learner is None:&lt;br /&gt;
        # FreeBSD pkg installs rspamc as a root-owned versioned symlink. Resolve&lt;br /&gt;
        # it once so preflight and training invoke the same trusted executable.&lt;br /&gt;
        executable = resolve_executable(executable)&lt;br /&gt;
        learner = admin.run_learning&lt;br /&gt;
    summary = {&amp;quot;learned&amp;quot;: 0, &amp;quot;duplicates&amp;quot;: 0, &amp;quot;quarantined&amp;quot;: 0, &amp;quot;invalid&amp;quot;: 0,&lt;br /&gt;
               &amp;quot;pending&amp;quot;: 0, &amp;quot;controller_unavailable&amp;quot;: False, &amp;quot;storage_blocked&amp;quot;: False,&lt;br /&gt;
               &amp;quot;archive_warning&amp;quot;: False}&lt;br /&gt;
    with admin.admin_lock(root):&lt;br /&gt;
        events = order_events(root)&lt;br /&gt;
        summary[&amp;quot;pending&amp;quot;] = len(events)&lt;br /&gt;
        if not events:&lt;br /&gt;
            return summary&lt;br /&gt;
        latest, blocked, sequence, used_bytes, archive_count = _journal(root)&lt;br /&gt;
        summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
        if used_bytes &amp;gt;= admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
            summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        if not preflight(executable, controller, password_file, timeout):&lt;br /&gt;
            summary[&amp;quot;controller_unavailable&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        handled = 0&lt;br /&gt;
        for candidate in events:&lt;br /&gt;
            if handled &amp;gt;= batch_size or (stop_event is not None and stop_event.is_set()):&lt;br /&gt;
                break&lt;br /&gt;
            if candidate.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            event_id = candidate[&amp;quot;id&amp;quot;]&lt;br /&gt;
            if (root / &amp;quot;processed&amp;quot; / event_id).exists():&lt;br /&gt;
                # Crash before prepare_archive moved pending/ID: its root-owned&lt;br /&gt;
                # intent is authoritative. Preserve the pending original for&lt;br /&gt;
                # manual recovery and continue independent content, never retry.&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            try:&lt;br /&gt;
                metadata, message = admin.read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
            except FileNotFoundError:&lt;br /&gt;
                continue&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            # Do not act if the queued ordering/identity changed since enumeration.&lt;br /&gt;
            if any(metadata.get(key) != candidate.get(key) for key in&lt;br /&gt;
                   (&amp;quot;sha256&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;created_ns&amp;quot;, &amp;quot;created_utc&amp;quot;)):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            if used_bytes + 2 * len(message) + 32768 &amp;gt; admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            label, digest = metadata[&amp;quot;label&amp;quot;], metadata[&amp;quot;sha256&amp;quot;]&lt;br /&gt;
            if candidate.get(&amp;quot;_ordering_invalid&amp;quot;):&lt;br /&gt;
                status, reason = &amp;quot;quarantined_ordering&amp;quot;, &amp;quot;event ordering is ambiguous&amp;quot;&lt;br /&gt;
            elif digest in blocked:&lt;br /&gt;
                status, reason = &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;a prior learning operation has an uncertain result&amp;quot;&lt;br /&gt;
            elif digest in latest and latest[digest][1] == label:&lt;br /&gt;
                status, reason = &amp;quot;duplicate&amp;quot;, &amp;quot;same class as the latest successful learning operation&amp;quot;&lt;br /&gt;
            else:&lt;br /&gt;
                status, reason = &amp;quot;learning&amp;quot;, &amp;quot;automatic explicit IMAP feedback&amp;quot;&lt;br /&gt;
            try:&lt;br /&gt;
                archive, receipt = admin.prepare_archive(root, event_id, metadata, message, status, label, reason)&lt;br /&gt;
            except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                # prepare_archive writes a root journal before moving the source;&lt;br /&gt;
                # stop instead of retrying an incompletely prepared operation.&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            sequence += 1&lt;br /&gt;
            receipt.update({&amp;quot;mode&amp;quot;: &amp;quot;automatic_imap_feedback&amp;quot;, &amp;quot;worker_sequence&amp;quot;: sequence,&lt;br /&gt;
                            &amp;quot;processed_ns&amp;quot;: time.time_ns(), &amp;quot;source_created_ns&amp;quot;: candidate[&amp;quot;_order_ns&amp;quot;]})&lt;br /&gt;
            if digest in latest:&lt;br /&gt;
                receipt[&amp;quot;previous_successful_class&amp;quot;] = latest[digest][1]&lt;br /&gt;
            # Durable intent exists before the only call that may change Bayes.&lt;br /&gt;
            admin.update_receipt(archive, receipt)&lt;br /&gt;
            if status == &amp;quot;learning&amp;quot;:&lt;br /&gt;
                try:&lt;br /&gt;
                    returncode = learner(message, label, executable=executable, controller=controller,&lt;br /&gt;
                                         password_file=password_file, timeout=timeout)&lt;br /&gt;
                except (subprocess.TimeoutExpired, OSError, FeedbackError):&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    receipt[&amp;quot;error&amp;quot;] = &amp;quot;client failure; server may have learned; no automatic retry&amp;quot;&lt;br /&gt;
                else:&lt;br /&gt;
                    receipt[&amp;quot;returncode&amp;quot;] = returncode&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if returncode == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    if returncode != 0:&lt;br /&gt;
                        receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; no automatic retry&amp;quot;&lt;br /&gt;
                receipt[&amp;quot;finished_utc&amp;quot;] = admin.collector.utc_now()&lt;br /&gt;
                admin.update_receipt(archive, receipt)&lt;br /&gt;
                if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                    latest[digest] = ((True, sequence, event_id), label)&lt;br /&gt;
                    summary[&amp;quot;learned&amp;quot;] += 1&lt;br /&gt;
                else:&lt;br /&gt;
                    blocked.add(digest)&lt;br /&gt;
                    summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            elif status == &amp;quot;duplicate&amp;quot;:&lt;br /&gt;
                summary[&amp;quot;duplicates&amp;quot;] += 1&lt;br /&gt;
            else:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
                summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            handled += 1&lt;br /&gt;
            used_bytes += 2 * len(message) + 32768&lt;br /&gt;
            archive_count += 1&lt;br /&gt;
            summary[&amp;quot;pending&amp;quot;] -= 1&lt;br /&gt;
            summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
            log_event(&amp;quot;feedback_processed&amp;quot;, id=event_id, label=label, status=receipt[&amp;quot;status&amp;quot;])&lt;br /&gt;
            if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learn_uncertain&amp;quot;:&lt;br /&gt;
                # The controller may have failed after the preflight. Leave all&lt;br /&gt;
                # other candidates pending until a fresh read-only probe passes.&lt;br /&gt;
                break&lt;br /&gt;
    return summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=admin.DEFAULT_ROOT)&lt;br /&gt;
    parser.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--password-file&amp;quot;, default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
    parser.add_argument(&amp;quot;--poll&amp;quot;, type=float, default=2)&lt;br /&gt;
    parser.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    parser.add_argument(&amp;quot;--batch-size&amp;quot;, type=int, default=20)&lt;br /&gt;
    parser.add_argument(&amp;quot;--once&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    admin.require_root()&lt;br /&gt;
    root = Path(args.queue_root)&lt;br /&gt;
    if not root.is_absolute() or not 0.2 &amp;lt;= args.poll &amp;lt;= 60 or not 0 &amp;lt; args.timeout &amp;lt;= 300 or not 1 &amp;lt;= args.batch_size &amp;lt;= 100:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid worker options&amp;quot;)&lt;br /&gt;
    admin.check_root_directory(root)&lt;br /&gt;
    stopping = threading.Event()&lt;br /&gt;
    for signum in (signal.SIGTERM, signal.SIGINT):&lt;br /&gt;
        signal.signal(signum, lambda *_: stopping.set())&lt;br /&gt;
    lockfd = os.open(root / &amp;quot;.worker.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)&lt;br /&gt;
        previous_state = None&lt;br /&gt;
        while not stopping.is_set():&lt;br /&gt;
            try:&lt;br /&gt;
                summary = process_pending(root, executable=args.rspamc_bin, controller=args.controller,&lt;br /&gt;
                                          password_file=args.password_file, timeout=args.timeout,&lt;br /&gt;
                                          batch_size=args.batch_size, stop_event=stopping)&lt;br /&gt;
                state = {key: value for key, value in summary.items() if key not in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)}&lt;br /&gt;
                if state != previous_state or any(summary[key] for key in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)):&lt;br /&gt;
                    log_event(&amp;quot;worker_state&amp;quot;, **summary)&lt;br /&gt;
                previous_state = state&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 2 if summary[&amp;quot;controller_unavailable&amp;quot;] or summary[&amp;quot;storage_blocked&amp;quot;] else 0&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                if previous_state != &amp;quot;local_error&amp;quot;:&lt;br /&gt;
                    log_event(&amp;quot;worker_error&amp;quot;, reason=&amp;quot;local queue, journal or configuration failure; no automatic retry of uncertain training&amp;quot;)&lt;br /&gt;
                previous_state = &amp;quot;local_error&amp;quot;&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 1&lt;br /&gt;
            stopping.wait(args.poll)&lt;br /&gt;
        return 0&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    try:&lt;br /&gt;
        raise SystemExit(main())&lt;br /&gt;
    except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
        log_event(&amp;quot;worker_start_failed&amp;quot;, reason=&amp;quot;root, configuration or worker lock required&amp;quot;)&lt;br /&gt;
        raise SystemExit(1)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== 16. Перенос почты с Яндекса через imapsync ==&lt;br /&gt;
&lt;br /&gt;
Однократное копирование переносит письма из существующего ящика Яндекса в существующий ящик Dovecot. Пример ниже использует &amp;lt;code&amp;gt;source@example.org&amp;lt;/code&amp;gt; как источник и &amp;lt;code&amp;gt;target@example.net&amp;lt;/code&amp;gt; на &amp;lt;code&amp;gt;mail.example.net&amp;lt;/code&amp;gt; как назначение. Замените эти значения своими. Переключать MX обратно на Яндекс для такого переноса не требуется.&lt;br /&gt;
&lt;br /&gt;
Папки источника размещаются внутри отдельной папки &amp;lt;code&amp;gt;Yandex-source&amp;lt;/code&amp;gt;. Оригиналы на Яндексе и текущая почта назначения сохраняются. Команды выполняются последовательно в одной интерактивной root-сессии SSH; после переподключения параметры &amp;lt;code&amp;gt;set --&amp;lt;/code&amp;gt; нужно задать заново.&lt;br /&gt;
&lt;br /&gt;
=== 16.1. Доступ и пакеты ===&lt;br /&gt;
&lt;br /&gt;
Разрешите IMAP в настройках почтовых программ Яндекса; для организации Яндекс 360 может потребоваться также разрешение протокола в кабинете администратора. Для источника используйте пароль приложения с доступом к Почте, для назначения — пароль своего ящика Dovecot. Если Яндекс блокирует IMAP и разрешить его нельзя, imapsync не сможет прочитать письма.&lt;br /&gt;
&lt;br /&gt;
Проверьте, что &amp;lt;code&amp;gt;/opt&amp;lt;/code&amp;gt; смонтирован как UFS, целевой ящик уже существует и имя &amp;lt;code&amp;gt;mail.example.net&amp;lt;/code&amp;gt; соответствует действительному сертификату почтового сервера. Импорт выполняется в обычное хранилище IMAP; файловый каталог пользователей Exim/Dovecot менять не требуется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
sudo pkg install imapsync&lt;br /&gt;
sudo /bin/sh&lt;br /&gt;
umask 077&lt;br /&gt;
install -d -o root -g wheel -m 0700 /opt/mail/migration/yandex-source&lt;br /&gt;
install -d -o root -g wheel -m 0700 /opt/mail/migration/yandex-source/cache /opt/mail/migration/yandex-source/log&lt;br /&gt;
cd /opt/mail/migration/yandex-source&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 16.2. Ввести пароли без отображения ===&lt;br /&gt;
&lt;br /&gt;
Следующие команды запрашивают пароли в терминале и записывают их в новые файлы. Пароли не попадают в аргументы процессов, переменные окружения или историю shell. С установленной выше маской файлы имеют права &amp;lt;code&amp;gt;0600&amp;lt;/code&amp;gt;, каталог — &amp;lt;code&amp;gt;0700&amp;lt;/code&amp;gt;. Режим создания &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; отказывается перезаписывать существующие файлы; не запускайте команды повторно, если пароли уже введены.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
python3.12 -c &#039;import getpass, os, sys; assert os.geteuid() == 0 and sys.stdin.isatty(); p = getpass.getpass(&amp;quot;Пароль приложения Яндекса: &amp;quot;); assert p and not any(c in p for c in &amp;quot;\x00\r\n&amp;quot;); f = open(&amp;quot;source.pass&amp;quot;, &amp;quot;x&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;); f.write(p + &amp;quot;\n&amp;quot;); f.close()&#039;&lt;br /&gt;
python3.12 -c &#039;import getpass, os, sys; assert os.geteuid() == 0 and sys.stdin.isatty(); p = getpass.getpass(&amp;quot;Пароль целевого ящика: &amp;quot;); assert p and not any(c in p for c in &amp;quot;\x00\r\n&amp;quot;); f = open(&amp;quot;target.pass&amp;quot;, &amp;quot;x&amp;quot;, encoding=&amp;quot;utf-8&amp;quot;); f.write(p + &amp;quot;\n&amp;quot;); f.close()&#039;&lt;br /&gt;
stat -f &#039;%Su:%Sg %Lp %N&#039; source.pass target.pass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 16.3. Задать параметры и проверить план ===&lt;br /&gt;
&lt;br /&gt;
В примере включена проверка цепочки сертификатов и имён обоих серверов, разрешён TLS 1.2 и новее. Даты доставки и флаги новых писем копируются; повторный запуск не перезаписывает флаги уже перенесённых сообщений. &amp;lt;code&amp;gt;--syncduplicates&amp;lt;/code&amp;gt; сохраняет также повторяющиеся экземпляры писем источника. Не меняйте способ сопоставления сообщений между запусками одного переноса.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
set -- \&lt;br /&gt;
  --host1 imap.yandex.ru --port1 993 --ssl1 \&lt;br /&gt;
  --user1 source@example.org --passfile1 /opt/mail/migration/yandex-source/source.pass \&lt;br /&gt;
  --sslargs1 SSL_verify_mode=1 \&lt;br /&gt;
  --sslargs1 SSL_verifycn_scheme=imap \&lt;br /&gt;
  --sslargs1 SSL_verifycn_name=imap.yandex.ru \&lt;br /&gt;
  --sslargs1 SSL_hostname=imap.yandex.ru \&lt;br /&gt;
  --sslargs1 SSL_ca_file=/etc/ssl/cert.pem \&lt;br /&gt;
  --sslargs1 SSL_version=SSLv23:!SSLv3:!SSLv2:!TLSv1:!TLSv1_1 \&lt;br /&gt;
  --host2 mail.example.net --port2 993 --ssl2 \&lt;br /&gt;
  --user2 target@example.net --passfile2 /opt/mail/migration/yandex-source/target.pass \&lt;br /&gt;
  --sslargs2 SSL_verify_mode=1 \&lt;br /&gt;
  --sslargs2 SSL_verifycn_scheme=imap \&lt;br /&gt;
  --sslargs2 SSL_verifycn_name=mail.example.net \&lt;br /&gt;
  --sslargs2 SSL_hostname=mail.example.net \&lt;br /&gt;
  --sslargs2 SSL_ca_file=/etc/ssl/cert.pem \&lt;br /&gt;
  --sslargs2 SSL_version=SSLv23:!SSLv3:!SSLv2:!TLSv1:!TLSv1_1 \&lt;br /&gt;
  --subfolder2 Yandex-source \&lt;br /&gt;
  --useheader Message-Id --syncduplicates --syncinternaldates --noresyncflags \&lt;br /&gt;
  --noexpunge1 --noexpunge2 --nouidexpunge2 \&lt;br /&gt;
  --noemailreport1 --noemailreport2 --noreleasecheck \&lt;br /&gt;
  --tmpdir /opt/mail/migration/yandex-source/cache \&lt;br /&gt;
  --logdir /opt/mail/migration/yandex-source/log \&lt;br /&gt;
  --pidfile /opt/mail/migration/yandex-source/imapsync.pid --pidfilelocking&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверки ниже не копируют письма. Первая должна подтвердить вход в оба ящика; следующая показывает соответствие папок, затем выводятся количество и объём писем. После любой ошибки сначала устраните её и повторите проверку, прежде чем продолжать.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --justlogin --logfile login.log&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --dry --justfolders --logfile folders.log&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --dry --justfoldersizes --logfile sizes.log&lt;br /&gt;
df -h /opt&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сопоставьте суммарный объём источника из &amp;lt;code&amp;gt;sizes.log&amp;lt;/code&amp;gt; со свободным местом &amp;lt;code&amp;gt;df&amp;lt;/code&amp;gt;. Для первого полного копирования оставьте запас не менее 1 ГиБ сверх объёма переносимых писем, а для большого числа маленьких писем — дополнительное место под файловые метаданные. Если места недостаточно, не начинайте копирование: сначала увеличьте раздел или уменьшите согласованный объём переноса. В выводе плана все создаваемые папки должны находиться под &amp;lt;code&amp;gt;Yandex-source&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== 16.4. Скопировать и проверить результат ===&lt;br /&gt;
&lt;br /&gt;
Во время ручного запуска держите SSH-сессию открытой. Не запускайте два переноса этой пары одновременно; файл PID дополнительно защищает от параллельного старта. Не добавляйте параметры удаления &amp;lt;code&amp;gt;--delete1&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;--delete2&amp;lt;/code&amp;gt; или удаления папок. Включённые &amp;lt;code&amp;gt;--noexpunge*&amp;lt;/code&amp;gt; запрещают очистку удалённых сообщений.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --logfile copy.log&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
После успешного завершения выполните повторную проверку с теми же параметрами:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
imapsync &amp;quot;$@&amp;quot; --dry --logfile verify.log&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверьте код завершения &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, отсутствие ошибок в итогах imapsync и отсутствие новых писем, предлагаемых к копированию в проверочном запуске. Если источник получил новые сообщения во время переноса, повторите обычное копирование с теми же параметрами и затем снова проверку. Откройте папку &amp;lt;code&amp;gt;Yandex-source&amp;lt;/code&amp;gt; и её подпапки в SnappyMail; выборочно сравните письма, вложения, даты и состояние прочитано/непрочитано с источником.&lt;br /&gt;
&lt;br /&gt;
Если перенос прервался, журналы и cache остаются в защищённом каталоге. После устранения причины повторите команду с прежними параметрами и cache. Перед перезапуском убедитесь, что предыдущий процесс завершён; наличие старого PID-файла само по себе не доказывает, что процесс ещё работает.&lt;br /&gt;
&lt;br /&gt;
После окончательной проверки и завершения всех процессов удалите временные файлы паролей:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
rm -f -- /opt/mail/migration/yandex-source/source.pass /opt/mail/migration/yandex-source/target.pass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Затем отзовите использованный пароль приложения Яндекса, если он был создан только для миграции. Сохранённые журналы доступны root и могут содержать адреса и названия папок; не публикуйте их без обезличивания.&lt;br /&gt;
&lt;br /&gt;
=== 16.5. Ошибки доступа ===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;Invalid credentials or IMAP is disabled&amp;lt;/code&amp;gt;: проверьте полный адрес источника, пароль приложения и разрешение IMAP у пользователя и организации. Обычный пароль аккаунта может не подходить для почтового клиента.&lt;br /&gt;
* Ошибка сертификата: исправьте имя сервера, время или цепочку сертификатов. Не отключайте &amp;lt;code&amp;gt;SSL_verify_mode&amp;lt;/code&amp;gt; и проверку имени.&lt;br /&gt;
* Недостаточно места: остановите перенос, освободите или увеличьте хранилище и повторите с прежними параметрами. Не удаляйте исходные письма ради освобождения места на назначении.&lt;br /&gt;
&lt;br /&gt;
Источники: [https://imapsync.lamiral.info/README руководство imapsync], [https://imapsync.lamiral.info/FAQ.d/FAQ.Yandex.txt параметры Яндекса], [https://imapsync.lamiral.info/FAQ.d/FAQ.Duplicates.txt дубликаты], [https://yandex.ru/support/yandex-360/business/admin/ru/mail/email-clients/ разрешение почтовых протоколов].&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=691</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=691"/>
		<updated>2026-10-29T23:56:53Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 1. Схема и требования */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет → TCP 25 / STARTTLS → Exim&lt;br /&gt;
Клиент   → TCP 465 TLS или 587 STARTTLS + AUTH → Exim&lt;br /&gt;
Exim     → LMTP Unix socket → Dovecot → /opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim     → AUTH Unix socket → Dovecot → файловый каталог пользователей&lt;br /&gt;
Клиент   → TCP 993 TLS → Dovecot&lt;br /&gt;
SnappyMail (необязательно) → те же IMAPS и SMTP AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
sysrc hostname=mail.example.org&lt;br /&gt;
hostname mail.example.org&lt;br /&gt;
sysrc defaultrouter=198.51.100.1&lt;br /&gt;
# Только если в работающей таблице отсутствует default route:&lt;br /&gt;
route add default 198.51.100.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo /dev/da1 | awk &#039;{print $3}&#039;)&lt;br /&gt;
test -n &amp;quot;$disk_bytes&amp;quot; &amp;amp;&amp;amp; test &amp;quot;$disk_bytes&amp;quot; -gt 0 || exit 1&lt;br /&gt;
cmp -s -n &amp;quot;$disk_bytes&amp;quot; /dev/da1 /dev/zero&lt;br /&gt;
printf &#039;cmp status: %s\n&#039; &amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
pkg update&lt;br /&gt;
pkg install -n exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
# После проверки плана:&lt;br /&gt;
pkg install -y exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
sysrc exim_enable=NO dovecot_enable=NO&lt;br /&gt;
sysrc sendmail_enable=NONE sendmail_submit_enable=NO&lt;br /&gt;
sysrc sendmail_outbound_enable=NO sendmail_msp_queue_enable=NO&lt;br /&gt;
service sendmail onestop&lt;br /&gt;
/usr/local/sbin/exim -bV&lt;br /&gt;
/usr/local/sbin/dovecot --version&lt;br /&gt;
pw usershow mailnull&lt;br /&gt;
pw groupshow mail&lt;br /&gt;
pw groupshow dovecot&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only review and explicit training of IMAPSieve feedback.&lt;br /&gt;
&lt;br /&gt;
No command trains automatically. list/show are local reads. learn ID spam|ham&lt;br /&gt;
is the only command that invokes rspamc. reject ID archives without training.&lt;br /&gt;
Queue root and processed must be root-owned, non-writable by mail users;&lt;br /&gt;
pending is separately owned by vmail, mode 0700. Archived originals remain in&lt;br /&gt;
processed/ID/source; only root-owned snapshots are used for training or review.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from contextlib import contextmanager&lt;br /&gt;
from email import policy&lt;br /&gt;
from email.parser import BytesHeaderParser&lt;br /&gt;
import hashlib&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import shutil&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
&lt;br /&gt;
spec = importlib.util.spec_from_file_location(&amp;quot;feedback_collector&amp;quot;, Path(__file__).with_name(&amp;quot;feedback.py&amp;quot;))&lt;br /&gt;
collector = importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE = collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError = collector.FeedbackError&lt;br /&gt;
ID_RE = re.compile(r&amp;quot;[0-9a-f]{32}\Z&amp;quot;)&lt;br /&gt;
DEFAULT_ROOT = &amp;quot;/opt/mail/rspamd-feedback&amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE = &amp;quot;/usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
HEADERS = (&amp;quot;From&amp;quot;, &amp;quot;To&amp;quot;, &amp;quot;Date&amp;quot;, &amp;quot;Subject&amp;quot;, &amp;quot;Message-ID&amp;quot;, &amp;quot;X-Spam-Status&amp;quot;, &amp;quot;X-Spam-Score&amp;quot;, &amp;quot;X-Rspamd-Action&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def emit(value):&lt;br /&gt;
    print(json.dumps(value, ensure_ascii=True, sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def require_root():&lt;br /&gt;
    if not hasattr(os, &amp;quot;geteuid&amp;quot;) or os.geteuid() != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;this administrative command requires root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_id(event_id):&lt;br /&gt;
    if not ID_RE.fullmatch(event_id):&lt;br /&gt;
        raise FeedbackError(&amp;quot;ID must be exactly 32 lowercase hexadecimal characters&amp;quot;)&lt;br /&gt;
    return event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Directory:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Nofollow directory descriptor on FreeBSD; portable fallback for local tests.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, path):&lt;br /&gt;
        self.path = Path(path)&lt;br /&gt;
        info = self.path.lstat()&lt;br /&gt;
        if not stat.S_ISDIR(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;expected a real directory&amp;quot;)&lt;br /&gt;
        self.fd = None&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            self.fd = os.open(self.path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
    def __enter__(self):&lt;br /&gt;
        return self&lt;br /&gt;
&lt;br /&gt;
    def __exit__(self, *args):&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
    def read(self, name, limit):&lt;br /&gt;
        if &amp;quot;/&amp;quot; in name or &amp;quot;\\&amp;quot; in name or name in (&amp;quot;.&amp;quot;, &amp;quot;..&amp;quot;):&lt;br /&gt;
            raise FeedbackError(&amp;quot;invalid internal filename&amp;quot;)&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=self.fd)&lt;br /&gt;
        else:&lt;br /&gt;
            path = self.path / name&lt;br /&gt;
            if path.is_symlink():&lt;br /&gt;
                raise FeedbackError(&amp;quot;symlink is not an event file&amp;quot;)&lt;br /&gt;
            fd = os.open(path, os.O_RDONLY | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0))&lt;br /&gt;
        with os.fdopen(fd, &amp;quot;rb&amp;quot;) as source:&lt;br /&gt;
            info = os.fstat(source.fileno())&lt;br /&gt;
            if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_size &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file is not a bounded, single-link regular file&amp;quot;)&lt;br /&gt;
            result = source.read(limit + 1)&lt;br /&gt;
            if len(result) &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file exceeded the size limit&amp;quot;)&lt;br /&gt;
            return result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def read_event(directory, event_id):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    # On POSIX open the event relative to the already opened parent descriptor.&lt;br /&gt;
    # The parent path is deployment-owned, and a symlink event is rejected.&lt;br /&gt;
    with Directory(Path(directory) / event_id) as entry:&lt;br /&gt;
        metadata = json.loads(entry.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
        message = entry.read(&amp;quot;message.eml&amp;quot;, MAX_MESSAGE)&lt;br /&gt;
    if not isinstance(metadata, dict) or not isinstance(metadata.get(&amp;quot;user&amp;quot;), str):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                             metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
    if metadata.get(&amp;quot;id&amp;quot;) != event_id or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    if metadata.get(&amp;quot;bytes&amp;quot;) != len(message) or metadata.get(&amp;quot;sha256&amp;quot;) != hashlib.sha256(message).hexdigest():&lt;br /&gt;
        raise FeedbackError(&amp;quot;event content does not match its metadata&amp;quot;)&lt;br /&gt;
    return metadata, message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def write_private(path, value, *, json_value=False):&lt;br /&gt;
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0)&lt;br /&gt;
    fd = os.open(path, flags, 0o600)&lt;br /&gt;
    with os.fdopen(fd, &amp;quot;wb&amp;quot;) as output:&lt;br /&gt;
        if json_value:&lt;br /&gt;
            value = (json.dumps(value, ensure_ascii=True, sort_keys=True) + &amp;quot;\n&amp;quot;).encode()&lt;br /&gt;
        output.write(value)&lt;br /&gt;
        output.flush()&lt;br /&gt;
        os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def update_receipt(directory, receipt):&lt;br /&gt;
    temporary = directory / &amp;quot;.admin-new.json&amp;quot;&lt;br /&gt;
    write_private(temporary, receipt, json_value=True)&lt;br /&gt;
    os.replace(temporary, directory / &amp;quot;admin.json&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_root_directory(path):&lt;br /&gt;
    with Directory(path):&lt;br /&gt;
        info = Path(path).stat()&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;administrative directories must be root-owned and not group/other writable&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def admin_lock(root):&lt;br /&gt;
    check_root_directory(root)&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        processed.mkdir(mode=0o700)&lt;br /&gt;
    check_root_directory(processed)&lt;br /&gt;
    fd = os.open(root / &amp;quot;.admin.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(fd, fcntl.LOCK_EX)&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def receipt_for(directory):&lt;br /&gt;
    with Directory(directory) as entry:&lt;br /&gt;
        return json.loads(entry.read(&amp;quot;admin.json&amp;quot;, 16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def find_prior(root, sha256):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return None&lt;br /&gt;
    for entry in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
            continue&lt;br /&gt;
        try:&lt;br /&gt;
            receipt = receipt_for(entry)&lt;br /&gt;
        except (OSError, ValueError, FeedbackError):&lt;br /&gt;
            # A damaged journal must never silently allow repeated training.&lt;br /&gt;
            raise FeedbackError(&amp;quot;processed journal is incomplete; inspect it before more training&amp;quot;)&lt;br /&gt;
        if receipt.get(&amp;quot;sha256&amp;quot;) == sha256 and receipt.get(&amp;quot;status&amp;quot;) in (&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learned&amp;quot;, &amp;quot;learn_uncertain&amp;quot;):&lt;br /&gt;
            return receipt&lt;br /&gt;
    return None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def prepare_archive(root, event_id, metadata, message, status, label=None, reason=None):&lt;br /&gt;
    if shutil.disk_usage(root).free &amp;lt; collector.MIN_FREE_BYTES + len(message) + 32768:&lt;br /&gt;
        raise FeedbackError(&amp;quot;insufficient free disk space to preserve the reviewed snapshot&amp;quot;)&lt;br /&gt;
    archive = root / &amp;quot;processed&amp;quot; / event_id&lt;br /&gt;
    archive.mkdir(mode=0o700)&lt;br /&gt;
    receipt = {&amp;quot;schema&amp;quot;: 1, &amp;quot;id&amp;quot;: event_id, &amp;quot;sha256&amp;quot;: metadata[&amp;quot;sha256&amp;quot;],&lt;br /&gt;
               &amp;quot;reviewed_utc&amp;quot;: collector.utc_now(), &amp;quot;status&amp;quot;: &amp;quot;preparing&amp;quot;,&lt;br /&gt;
               &amp;quot;selected_class&amp;quot;: label, &amp;quot;reason&amp;quot;: reason}&lt;br /&gt;
    write_private(archive / &amp;quot;admin.json&amp;quot;, receipt, json_value=True)&lt;br /&gt;
    write_private(archive / &amp;quot;message.eml&amp;quot;, message)&lt;br /&gt;
    write_private(archive / &amp;quot;metadata.json&amp;quot;, json.dumps(metadata, ensure_ascii=True).encode())&lt;br /&gt;
    # Keep the complete original event. Never traverse or execute files in source/.&lt;br /&gt;
    os.rename(root / &amp;quot;pending&amp;quot; / event_id, archive / &amp;quot;source&amp;quot;)&lt;br /&gt;
    receipt[&amp;quot;status&amp;quot;] = status&lt;br /&gt;
    update_receipt(archive, receipt)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;pending&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;processed&amp;quot;)&lt;br /&gt;
    return archive, receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def run_learning(message, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    executable = Path(executable)&lt;br /&gt;
    if not executable.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be an absolute path&amp;quot;)&lt;br /&gt;
    command = [str(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be an absolute regular file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be root-owned, mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        # rspamc reads the file itself; the secret never appears in argv or logs.&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;learn_&amp;quot; + label)&lt;br /&gt;
    # No -u/-d: one shared Bayes classifier. No shell and no automatic retry.&lt;br /&gt;
    return subprocess.run(command, input=message, stdout=subprocess.DEVNULL,&lt;br /&gt;
                          stderr=subprocess.DEVNULL, timeout=timeout, check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def learn_event(root, event_id, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid training class&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        prior = find_prior(root, metadata[&amp;quot;sha256&amp;quot;])&lt;br /&gt;
        if prior:&lt;br /&gt;
            raise FeedbackError(&amp;quot;this content has a prior learned or uncertain event; inspect it and reject the duplicate&amp;quot;)&lt;br /&gt;
        archive, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;learning&amp;quot;, label)&lt;br /&gt;
        try:&lt;br /&gt;
            rc = run_learning(message, label, executable=executable, controller=controller,&lt;br /&gt;
                              password_file=password_file, timeout=timeout)&lt;br /&gt;
        except subprocess.TimeoutExpired:&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client timeout; server may have learned; do not automatically retry&amp;quot;&lt;br /&gt;
        except (OSError, FeedbackError):&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_failed&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client could not start or local configuration was invalid&amp;quot;&lt;br /&gt;
        else:&lt;br /&gt;
            receipt[&amp;quot;returncode&amp;quot;] = rc&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if rc == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            if rc:&lt;br /&gt;
                receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; inspect server before any retry&amp;quot;&lt;br /&gt;
        receipt[&amp;quot;finished_utc&amp;quot;] = collector.utc_now()&lt;br /&gt;
        update_receipt(archive, receipt)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def reject_event(root, event_id, reason=&amp;quot;review rejected&amp;quot;):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if len(reason) &amp;gt; 500:&lt;br /&gt;
        raise FeedbackError(&amp;quot;reason is too long&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        _, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;rejected&amp;quot;, reason=reason)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def list_events(root, state, limit):&lt;br /&gt;
    count = 0&lt;br /&gt;
    for name in ((&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;) if state == &amp;quot;all&amp;quot; else (state,)):&lt;br /&gt;
        directory = root / name&lt;br /&gt;
        if not directory.exists():&lt;br /&gt;
            continue&lt;br /&gt;
        with Directory(directory):&lt;br /&gt;
            for entry in directory.iterdir():&lt;br /&gt;
                if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                if count &amp;gt;= limit:&lt;br /&gt;
                    emit({&amp;quot;notice&amp;quot;: &amp;quot;limit reached&amp;quot;, &amp;quot;limit&amp;quot;: limit})&lt;br /&gt;
                    return&lt;br /&gt;
                try:&lt;br /&gt;
                    with Directory(entry) as item:&lt;br /&gt;
                        metadata = json.loads(item.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    row = {key: metadata.get(key) for key in (&amp;quot;id&amp;quot;, &amp;quot;created_utc&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;user&amp;quot;, &amp;quot;source&amp;quot;, &amp;quot;destination&amp;quot;, &amp;quot;bytes&amp;quot;)}&lt;br /&gt;
                    row[&amp;quot;state&amp;quot;] = name&lt;br /&gt;
                    if name == &amp;quot;processed&amp;quot;:&lt;br /&gt;
                        row[&amp;quot;review&amp;quot;] = receipt_for(entry)&lt;br /&gt;
                    emit(row)&lt;br /&gt;
                except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                    emit({&amp;quot;id&amp;quot;: entry.name, &amp;quot;state&amp;quot;: name, &amp;quot;error&amp;quot;: &amp;quot;invalid event; manual inspection required&amp;quot;})&lt;br /&gt;
                count += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def show_event(root, event_id, state, path_only):&lt;br /&gt;
    metadata, message = read_event(root / state, check_id(event_id))&lt;br /&gt;
    if path_only:&lt;br /&gt;
        emit({&amp;quot;id&amp;quot;: event_id, &amp;quot;message_path&amp;quot;: str(root / state / event_id / &amp;quot;message.eml&amp;quot;)})&lt;br /&gt;
        return&lt;br /&gt;
    headers = BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message), 262144)])&lt;br /&gt;
    emit({&amp;quot;metadata&amp;quot;: metadata, &amp;quot;headers&amp;quot;: {name: [str(v)[:2000] for v in headers.get_all(name, [])[:10]] for name in HEADERS}})&lt;br /&gt;
    if state == &amp;quot;processed&amp;quot;:&lt;br /&gt;
        emit({&amp;quot;review&amp;quot;: receipt_for(root / state / event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def warn_processed_storage(root):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return&lt;br /&gt;
    total = count = 0&lt;br /&gt;
    for event in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(event.name) or event.is_symlink() or not event.is_dir():&lt;br /&gt;
            continue&lt;br /&gt;
        count += 1&lt;br /&gt;
        for prefix in (event, event / &amp;quot;source&amp;quot;):&lt;br /&gt;
            if prefix.is_symlink():&lt;br /&gt;
                continue&lt;br /&gt;
            for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    info = (prefix / name).lstat()&lt;br /&gt;
                    if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                        total += info.st_size&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    pass&lt;br /&gt;
    if total &amp;gt;= collector.MAX_PENDING_BYTES or count &amp;gt;= collector.MAX_PENDING_EVENTS:&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: WARNING processed archive reached 512 MiB or 1024 events; review retention manually; nothing deleted&amp;quot;, file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=DEFAULT_ROOT)&lt;br /&gt;
    sub = parser.add_subparsers(dest=&amp;quot;command&amp;quot;, required=True)&lt;br /&gt;
    listing = sub.add_parser(&amp;quot;list&amp;quot;, help=&amp;quot;JSON lines, metadata only&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;, &amp;quot;all&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--limit&amp;quot;, type=int, default=100)&lt;br /&gt;
    showing = sub.add_parser(&amp;quot;show&amp;quot;, help=&amp;quot;selected headers only; never dumps the body&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--path&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning = sub.add_parser(&amp;quot;learn&amp;quot;, help=&amp;quot;explicitly train one reviewed event, then archive it&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;label&amp;quot;, choices=(&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;))&lt;br /&gt;
    learning.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--password-file&amp;quot;, default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
                          help=&amp;quot;root-only plaintext secret file; use --no-password only for an intentionally trusted local controller&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--no-password&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    rejecting = sub.add_parser(&amp;quot;reject&amp;quot;, help=&amp;quot;archive one event without training or deleting it&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;--reason&amp;quot;, default=&amp;quot;review rejected&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    try:&lt;br /&gt;
        require_root()&lt;br /&gt;
        root = Path(args.queue_root)&lt;br /&gt;
        if not root.is_absolute():&lt;br /&gt;
            raise FeedbackError(&amp;quot;queue root must be absolute&amp;quot;)&lt;br /&gt;
        check_root_directory(root)&lt;br /&gt;
        warn_processed_storage(root)&lt;br /&gt;
        if args.command == &amp;quot;list&amp;quot;:&lt;br /&gt;
            if not 1 &amp;lt;= args.limit &amp;lt;= 10000:&lt;br /&gt;
                raise FeedbackError(&amp;quot;limit must be between 1 and 10000&amp;quot;)&lt;br /&gt;
            list_events(root, args.state, args.limit)&lt;br /&gt;
        elif args.command == &amp;quot;show&amp;quot;:&lt;br /&gt;
            show_event(root, args.id, args.state, args.path)&lt;br /&gt;
        elif args.command == &amp;quot;reject&amp;quot;:&lt;br /&gt;
            emit(reject_event(root, args.id, args.reason))&lt;br /&gt;
        else:&lt;br /&gt;
            if not 0 &amp;lt; args.timeout &amp;lt;= 300:&lt;br /&gt;
                raise FeedbackError(&amp;quot;timeout must be between 0 and 300 seconds&amp;quot;)&lt;br /&gt;
            result = learn_event(root, args.id, args.label, executable=args.rspamc_bin,&lt;br /&gt;
                                 controller=args.controller, password_file=None if args.no_password else args.password_file,&lt;br /&gt;
                                 timeout=args.timeout)&lt;br /&gt;
            emit(result)&lt;br /&gt;
            return 0 if result[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot; else 2&lt;br /&gt;
        return 0&lt;br /&gt;
    except (FeedbackError, OSError, ValueError, TypeError):&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: operation refused or failed; inspect configuration/event locally&amp;quot;, file=sys.stderr)&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    raise SystemExit(main())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only background learning from the bounded IMAPSieve feedback queue.&lt;br /&gt;
&lt;br /&gt;
The collector remains a local spool writer. This process owns controller&lt;br /&gt;
credentials, learns only validated INBOX/Junk events, preserves every original,&lt;br /&gt;
and never retries a training operation with an uncertain result. Run foreground&lt;br /&gt;
under FreeBSD daemon(8), or use --once for one bounded batch.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import signal&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
import threading&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
_spec = importlib.util.spec_from_file_location(&amp;quot;feedback_worker_admin&amp;quot;, Path(__file__).with_name(&amp;quot;feedback-admin.py&amp;quot;))&lt;br /&gt;
admin = importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError = admin.FeedbackError&lt;br /&gt;
SHA_RE = re.compile(r&amp;quot;[0-9a-f]{64}\Z&amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS = 20000&lt;br /&gt;
UNCERTAIN = {&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learn_uncertain&amp;quot;, &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;quarantined_ordering&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def log_event(kind, **fields):&lt;br /&gt;
    # Callers pass only fixed diagnostics, numeric counts, event IDs and classes.&lt;br /&gt;
    print(json.dumps({&amp;quot;event&amp;quot;: kind, **fields}, ensure_ascii=True, sort_keys=True), flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _timestamp_ns(value):&lt;br /&gt;
    if not isinstance(value, str):&lt;br /&gt;
        raise ValueError(&amp;quot;missing timestamp&amp;quot;)&lt;br /&gt;
    parsed = datetime.fromisoformat(value.replace(&amp;quot;Z&amp;quot;, &amp;quot;+00:00&amp;quot;))&lt;br /&gt;
    if parsed.tzinfo is None:&lt;br /&gt;
        raise ValueError(&amp;quot;timestamp must have timezone&amp;quot;)&lt;br /&gt;
    return int(parsed.timestamp() * 1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def order_events(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read bounded metadata, ordered by collector created_ns, then event ID.&lt;br /&gt;
&lt;br /&gt;
    Legacy second-resolution timestamps are permitted only when the same&lt;br /&gt;
    content has no contradictory labels at an identical timestamp. Such a tie&lt;br /&gt;
    cannot be resolved from random event IDs and is quarantined without learning.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    pending = Path(root) / &amp;quot;pending&amp;quot;&lt;br /&gt;
    rows = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    with admin.Directory(pending):&lt;br /&gt;
        with os.scandir(pending) as entries:&lt;br /&gt;
            for entry in entries:&lt;br /&gt;
                if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                count += 1&lt;br /&gt;
                if count &amp;gt; admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
                    raise FeedbackError(&amp;quot;pending queue scan bound exceeded&amp;quot;)&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(pending / entry.name) as directory:&lt;br /&gt;
                        metadata = json.loads(directory.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    if not isinstance(metadata, dict):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid metadata&amp;quot;)&lt;br /&gt;
                    metadata = dict(metadata)&lt;br /&gt;
                    if metadata.get(&amp;quot;id&amp;quot;) != entry.name or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid event identity&amp;quot;)&lt;br /&gt;
                    if not isinstance(metadata.get(&amp;quot;sha256&amp;quot;), str) or not SHA_RE.fullmatch(metadata[&amp;quot;sha256&amp;quot;]):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid digest&amp;quot;)&lt;br /&gt;
                    admin.collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                                                   metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
                    timestamp = metadata.get(&amp;quot;created_ns&amp;quot;)&lt;br /&gt;
                    if isinstance(timestamp, int) and not isinstance(timestamp, bool) and timestamp &amp;gt; 0:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = timestamp&lt;br /&gt;
                    elif &amp;quot;created_ns&amp;quot; not in metadata:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = _timestamp_ns(metadata.get(&amp;quot;created_utc&amp;quot;))&lt;br /&gt;
                    else:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = 0&lt;br /&gt;
                        metadata[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
                    rows.append(metadata)&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    # A manual administrator may have archived this candidate.&lt;br /&gt;
                    continue&lt;br /&gt;
                except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                    rows.append({&amp;quot;id&amp;quot;: entry.name, &amp;quot;_invalid&amp;quot;: True, &amp;quot;_order_ns&amp;quot;: 0})&lt;br /&gt;
    tied = {}&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
            tied.setdefault((row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;]), set()).add(row[&amp;quot;label&amp;quot;])&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;) and len(tied[(row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;])]) &amp;gt; 1:&lt;br /&gt;
            row[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
    return sorted(rows, key=lambda row: (row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;id&amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _journal(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read root-owned receipts; successful classes use durable worker sequence.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    processed = Path(root) / &amp;quot;processed&amp;quot;&lt;br /&gt;
    latest, blocked = {}, set()&lt;br /&gt;
    max_sequence = used_bytes = count = 0&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
    admin.check_root_directory(processed)&lt;br /&gt;
    with os.scandir(processed) as entries:&lt;br /&gt;
        for entry in entries:&lt;br /&gt;
            if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                continue&lt;br /&gt;
            count += 1&lt;br /&gt;
            if count &amp;gt; MAX_JOURNAL_EVENTS:&lt;br /&gt;
                raise FeedbackError(&amp;quot;processed journal scan bound exceeded&amp;quot;)&lt;br /&gt;
            directory = processed / entry.name&lt;br /&gt;
            receipt = admin.receipt_for(directory)&lt;br /&gt;
            if not isinstance(receipt, dict) or receipt.get(&amp;quot;id&amp;quot;) != entry.name:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed receipt&amp;quot;)&lt;br /&gt;
            digest = receipt.get(&amp;quot;sha256&amp;quot;)&lt;br /&gt;
            if not isinstance(digest, str) or not SHA_RE.fullmatch(digest):&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed digest&amp;quot;)&lt;br /&gt;
            sequence = receipt.get(&amp;quot;worker_sequence&amp;quot;, 0)&lt;br /&gt;
            if not isinstance(sequence, int) or isinstance(sequence, bool) or sequence &amp;lt; 0:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed sequence&amp;quot;)&lt;br /&gt;
            max_sequence = max(max_sequence, sequence)&lt;br /&gt;
            status = receipt.get(&amp;quot;status&amp;quot;)&lt;br /&gt;
            if status in UNCERTAIN:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
            if status == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                label = receipt.get(&amp;quot;selected_class&amp;quot;)&lt;br /&gt;
                if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
                    raise FeedbackError(&amp;quot;invalid learned class&amp;quot;)&lt;br /&gt;
                # Existing manual receipts predate automatic processing; the old&lt;br /&gt;
                # admin refuses further training of a SHA once it has a receipt.&lt;br /&gt;
                fallback = _timestamp_ns(receipt.get(&amp;quot;finished_utc&amp;quot;, receipt.get(&amp;quot;reviewed_utc&amp;quot;)))&lt;br /&gt;
                position = (bool(sequence), sequence if sequence else fallback, entry.name)&lt;br /&gt;
                if digest not in latest or position &amp;gt; latest[digest][0]:&lt;br /&gt;
                    latest[digest] = (position, label)&lt;br /&gt;
            # Count snapshots and preserved originals, never recursively walk&lt;br /&gt;
            # arbitrary files in the mail-user-owned source/ directory.&lt;br /&gt;
            for part in (directory, directory / &amp;quot;source&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(part) as safe:&lt;br /&gt;
                        for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                            try:&lt;br /&gt;
                                if safe.fd is not None:&lt;br /&gt;
                                    info = os.stat(name, dir_fd=safe.fd, follow_symlinks=False)&lt;br /&gt;
                                else:&lt;br /&gt;
                                    info = (part / name).lstat()&lt;br /&gt;
                                if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                                    used_bytes += info.st_size&lt;br /&gt;
                            except FileNotFoundError:&lt;br /&gt;
                                continue&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    continue&lt;br /&gt;
    return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _check_trusted_parent_chain(path):&lt;br /&gt;
    if os.name != &amp;quot;posix&amp;quot;:&lt;br /&gt;
        return&lt;br /&gt;
    for parent in Path(path).parents:&lt;br /&gt;
        link_info = parent.lstat()&lt;br /&gt;
        info = parent.stat()&lt;br /&gt;
        if (link_info.st_uid != 0 or not stat.S_ISDIR(info.st_mode)&lt;br /&gt;
                or info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;executable parent directories must be controlled by root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def resolve_executable(executable):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Allow FreeBSD package symlinks, but pin both operations to their target.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    source = Path(executable)&lt;br /&gt;
    if not source.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be absolute&amp;quot;)&lt;br /&gt;
    source_info = source.lstat()&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and source_info.st_uid != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc entry must be owned by root&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(source)&lt;br /&gt;
    target = source.resolve(strict=True)&lt;br /&gt;
    info = target.lstat()&lt;br /&gt;
    if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be a regular file&amp;quot;)&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be root-owned and not writable by mail users&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(target)&lt;br /&gt;
    return str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def controller_ready(executable, controller, password_file, timeout):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Authenticated read-only controller probe; no candidate is archived yet.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    command = [resolve_executable(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;unsafe controller password file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;controller password file must be root-owned mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;stat&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        return subprocess.run(command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,&lt;br /&gt;
                              stderr=subprocess.DEVNULL, check=False, timeout=min(timeout, 5)).returncode == 0&lt;br /&gt;
    except (subprocess.TimeoutExpired, OSError):&lt;br /&gt;
        return False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def process_pending(root, *, executable=&amp;quot;/usr/local/bin/rspamc&amp;quot;, controller=&amp;quot;127.0.0.1:11334&amp;quot;,&lt;br /&gt;
                    password_file=admin.DEFAULT_PASSWORD_FILE, timeout=30, batch_size=20,&lt;br /&gt;
                    preflight=None, learner=None, stop_event=None):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Process at most batch_size candidates. Inject probes/learner for unit tests.&lt;br /&gt;
&lt;br /&gt;
    One admin lock serializes manual and background operations. A crash after a&lt;br /&gt;
    durable learning intent leaves status=learning, which blocks this SHA on the&lt;br /&gt;
    next pass; subsequent candidates are preserved as quarantined_uncertain.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    root = Path(root)&lt;br /&gt;
    preflight = controller_ready if preflight is None else preflight&lt;br /&gt;
    if learner is None:&lt;br /&gt;
        # FreeBSD pkg installs rspamc as a root-owned versioned symlink. Resolve&lt;br /&gt;
        # it once so preflight and training invoke the same trusted executable.&lt;br /&gt;
        executable = resolve_executable(executable)&lt;br /&gt;
        learner = admin.run_learning&lt;br /&gt;
    summary = {&amp;quot;learned&amp;quot;: 0, &amp;quot;duplicates&amp;quot;: 0, &amp;quot;quarantined&amp;quot;: 0, &amp;quot;invalid&amp;quot;: 0,&lt;br /&gt;
               &amp;quot;pending&amp;quot;: 0, &amp;quot;controller_unavailable&amp;quot;: False, &amp;quot;storage_blocked&amp;quot;: False,&lt;br /&gt;
               &amp;quot;archive_warning&amp;quot;: False}&lt;br /&gt;
    with admin.admin_lock(root):&lt;br /&gt;
        events = order_events(root)&lt;br /&gt;
        summary[&amp;quot;pending&amp;quot;] = len(events)&lt;br /&gt;
        if not events:&lt;br /&gt;
            return summary&lt;br /&gt;
        latest, blocked, sequence, used_bytes, archive_count = _journal(root)&lt;br /&gt;
        summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
        if used_bytes &amp;gt;= admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
            summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        if not preflight(executable, controller, password_file, timeout):&lt;br /&gt;
            summary[&amp;quot;controller_unavailable&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        handled = 0&lt;br /&gt;
        for candidate in events:&lt;br /&gt;
            if handled &amp;gt;= batch_size or (stop_event is not None and stop_event.is_set()):&lt;br /&gt;
                break&lt;br /&gt;
            if candidate.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            event_id = candidate[&amp;quot;id&amp;quot;]&lt;br /&gt;
            if (root / &amp;quot;processed&amp;quot; / event_id).exists():&lt;br /&gt;
                # Crash before prepare_archive moved pending/ID: its root-owned&lt;br /&gt;
                # intent is authoritative. Preserve the pending original for&lt;br /&gt;
                # manual recovery and continue independent content, never retry.&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            try:&lt;br /&gt;
                metadata, message = admin.read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
            except FileNotFoundError:&lt;br /&gt;
                continue&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            # Do not act if the queued ordering/identity changed since enumeration.&lt;br /&gt;
            if any(metadata.get(key) != candidate.get(key) for key in&lt;br /&gt;
                   (&amp;quot;sha256&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;created_ns&amp;quot;, &amp;quot;created_utc&amp;quot;)):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            if used_bytes + 2 * len(message) + 32768 &amp;gt; admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            label, digest = metadata[&amp;quot;label&amp;quot;], metadata[&amp;quot;sha256&amp;quot;]&lt;br /&gt;
            if candidate.get(&amp;quot;_ordering_invalid&amp;quot;):&lt;br /&gt;
                status, reason = &amp;quot;quarantined_ordering&amp;quot;, &amp;quot;event ordering is ambiguous&amp;quot;&lt;br /&gt;
            elif digest in blocked:&lt;br /&gt;
                status, reason = &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;a prior learning operation has an uncertain result&amp;quot;&lt;br /&gt;
            elif digest in latest and latest[digest][1] == label:&lt;br /&gt;
                status, reason = &amp;quot;duplicate&amp;quot;, &amp;quot;same class as the latest successful learning operation&amp;quot;&lt;br /&gt;
            else:&lt;br /&gt;
                status, reason = &amp;quot;learning&amp;quot;, &amp;quot;automatic explicit IMAP feedback&amp;quot;&lt;br /&gt;
            try:&lt;br /&gt;
                archive, receipt = admin.prepare_archive(root, event_id, metadata, message, status, label, reason)&lt;br /&gt;
            except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                # prepare_archive writes a root journal before moving the source;&lt;br /&gt;
                # stop instead of retrying an incompletely prepared operation.&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            sequence += 1&lt;br /&gt;
            receipt.update({&amp;quot;mode&amp;quot;: &amp;quot;automatic_imap_feedback&amp;quot;, &amp;quot;worker_sequence&amp;quot;: sequence,&lt;br /&gt;
                            &amp;quot;processed_ns&amp;quot;: time.time_ns(), &amp;quot;source_created_ns&amp;quot;: candidate[&amp;quot;_order_ns&amp;quot;]})&lt;br /&gt;
            if digest in latest:&lt;br /&gt;
                receipt[&amp;quot;previous_successful_class&amp;quot;] = latest[digest][1]&lt;br /&gt;
            # Durable intent exists before the only call that may change Bayes.&lt;br /&gt;
            admin.update_receipt(archive, receipt)&lt;br /&gt;
            if status == &amp;quot;learning&amp;quot;:&lt;br /&gt;
                try:&lt;br /&gt;
                    returncode = learner(message, label, executable=executable, controller=controller,&lt;br /&gt;
                                         password_file=password_file, timeout=timeout)&lt;br /&gt;
                except (subprocess.TimeoutExpired, OSError, FeedbackError):&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    receipt[&amp;quot;error&amp;quot;] = &amp;quot;client failure; server may have learned; no automatic retry&amp;quot;&lt;br /&gt;
                else:&lt;br /&gt;
                    receipt[&amp;quot;returncode&amp;quot;] = returncode&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if returncode == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    if returncode != 0:&lt;br /&gt;
                        receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; no automatic retry&amp;quot;&lt;br /&gt;
                receipt[&amp;quot;finished_utc&amp;quot;] = admin.collector.utc_now()&lt;br /&gt;
                admin.update_receipt(archive, receipt)&lt;br /&gt;
                if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                    latest[digest] = ((True, sequence, event_id), label)&lt;br /&gt;
                    summary[&amp;quot;learned&amp;quot;] += 1&lt;br /&gt;
                else:&lt;br /&gt;
                    blocked.add(digest)&lt;br /&gt;
                    summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            elif status == &amp;quot;duplicate&amp;quot;:&lt;br /&gt;
                summary[&amp;quot;duplicates&amp;quot;] += 1&lt;br /&gt;
            else:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
                summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            handled += 1&lt;br /&gt;
            used_bytes += 2 * len(message) + 32768&lt;br /&gt;
            archive_count += 1&lt;br /&gt;
            summary[&amp;quot;pending&amp;quot;] -= 1&lt;br /&gt;
            summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
            log_event(&amp;quot;feedback_processed&amp;quot;, id=event_id, label=label, status=receipt[&amp;quot;status&amp;quot;])&lt;br /&gt;
            if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learn_uncertain&amp;quot;:&lt;br /&gt;
                # The controller may have failed after the preflight. Leave all&lt;br /&gt;
                # other candidates pending until a fresh read-only probe passes.&lt;br /&gt;
                break&lt;br /&gt;
    return summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=admin.DEFAULT_ROOT)&lt;br /&gt;
    parser.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--password-file&amp;quot;, default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
    parser.add_argument(&amp;quot;--poll&amp;quot;, type=float, default=2)&lt;br /&gt;
    parser.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    parser.add_argument(&amp;quot;--batch-size&amp;quot;, type=int, default=20)&lt;br /&gt;
    parser.add_argument(&amp;quot;--once&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    admin.require_root()&lt;br /&gt;
    root = Path(args.queue_root)&lt;br /&gt;
    if not root.is_absolute() or not 0.2 &amp;lt;= args.poll &amp;lt;= 60 or not 0 &amp;lt; args.timeout &amp;lt;= 300 or not 1 &amp;lt;= args.batch_size &amp;lt;= 100:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid worker options&amp;quot;)&lt;br /&gt;
    admin.check_root_directory(root)&lt;br /&gt;
    stopping = threading.Event()&lt;br /&gt;
    for signum in (signal.SIGTERM, signal.SIGINT):&lt;br /&gt;
        signal.signal(signum, lambda *_: stopping.set())&lt;br /&gt;
    lockfd = os.open(root / &amp;quot;.worker.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)&lt;br /&gt;
        previous_state = None&lt;br /&gt;
        while not stopping.is_set():&lt;br /&gt;
            try:&lt;br /&gt;
                summary = process_pending(root, executable=args.rspamc_bin, controller=args.controller,&lt;br /&gt;
                                          password_file=args.password_file, timeout=args.timeout,&lt;br /&gt;
                                          batch_size=args.batch_size, stop_event=stopping)&lt;br /&gt;
                state = {key: value for key, value in summary.items() if key not in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)}&lt;br /&gt;
                if state != previous_state or any(summary[key] for key in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)):&lt;br /&gt;
                    log_event(&amp;quot;worker_state&amp;quot;, **summary)&lt;br /&gt;
                previous_state = state&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 2 if summary[&amp;quot;controller_unavailable&amp;quot;] or summary[&amp;quot;storage_blocked&amp;quot;] else 0&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                if previous_state != &amp;quot;local_error&amp;quot;:&lt;br /&gt;
                    log_event(&amp;quot;worker_error&amp;quot;, reason=&amp;quot;local queue, journal or configuration failure; no automatic retry of uncertain training&amp;quot;)&lt;br /&gt;
                previous_state = &amp;quot;local_error&amp;quot;&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 1&lt;br /&gt;
            stopping.wait(args.poll)&lt;br /&gt;
        return 0&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    try:&lt;br /&gt;
        raise SystemExit(main())&lt;br /&gt;
    except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
        log_event(&amp;quot;worker_start_failed&amp;quot;, reason=&amp;quot;root, configuration or worker lock required&amp;quot;)&lt;br /&gt;
        raise SystemExit(1)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=690</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=690"/>
		<updated>2026-10-29T23:55:17Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 1. Схема и требования */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет → TCP 25 / STARTTLS → Exim&lt;br /&gt;
Клиент   → TCP 465 TLS или 587 STARTTLS + AUTH → Exim&lt;br /&gt;
Exim     → LMTP Unix socket → Dovecot → /opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim     → AUTH Unix socket → Dovecot → файловый каталог пользователей&lt;br /&gt;
Клиент   → TCP 993 TLS → Dovecot&lt;br /&gt;
SnappyMail (необязательно) → те же IMAPS и SMTP AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;indicator name=&amp;quot;tested&amp;quot;&amp;gt;Проверено на FreeBSD 15.1&amp;lt;/indicator&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
sysrc hostname=mail.example.org&lt;br /&gt;
hostname mail.example.org&lt;br /&gt;
sysrc defaultrouter=198.51.100.1&lt;br /&gt;
# Только если в работающей таблице отсутствует default route:&lt;br /&gt;
route add default 198.51.100.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo /dev/da1 | awk &#039;{print $3}&#039;)&lt;br /&gt;
test -n &amp;quot;$disk_bytes&amp;quot; &amp;amp;&amp;amp; test &amp;quot;$disk_bytes&amp;quot; -gt 0 || exit 1&lt;br /&gt;
cmp -s -n &amp;quot;$disk_bytes&amp;quot; /dev/da1 /dev/zero&lt;br /&gt;
printf &#039;cmp status: %s\n&#039; &amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
pkg update&lt;br /&gt;
pkg install -n exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
# После проверки плана:&lt;br /&gt;
pkg install -y exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
sysrc exim_enable=NO dovecot_enable=NO&lt;br /&gt;
sysrc sendmail_enable=NONE sendmail_submit_enable=NO&lt;br /&gt;
sysrc sendmail_outbound_enable=NO sendmail_msp_queue_enable=NO&lt;br /&gt;
service sendmail onestop&lt;br /&gt;
/usr/local/sbin/exim -bV&lt;br /&gt;
/usr/local/sbin/dovecot --version&lt;br /&gt;
pw usershow mailnull&lt;br /&gt;
pw groupshow mail&lt;br /&gt;
pw groupshow dovecot&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only review and explicit training of IMAPSieve feedback.&lt;br /&gt;
&lt;br /&gt;
No command trains automatically. list/show are local reads. learn ID spam|ham&lt;br /&gt;
is the only command that invokes rspamc. reject ID archives without training.&lt;br /&gt;
Queue root and processed must be root-owned, non-writable by mail users;&lt;br /&gt;
pending is separately owned by vmail, mode 0700. Archived originals remain in&lt;br /&gt;
processed/ID/source; only root-owned snapshots are used for training or review.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from contextlib import contextmanager&lt;br /&gt;
from email import policy&lt;br /&gt;
from email.parser import BytesHeaderParser&lt;br /&gt;
import hashlib&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import shutil&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
&lt;br /&gt;
spec = importlib.util.spec_from_file_location(&amp;quot;feedback_collector&amp;quot;, Path(__file__).with_name(&amp;quot;feedback.py&amp;quot;))&lt;br /&gt;
collector = importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE = collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError = collector.FeedbackError&lt;br /&gt;
ID_RE = re.compile(r&amp;quot;[0-9a-f]{32}\Z&amp;quot;)&lt;br /&gt;
DEFAULT_ROOT = &amp;quot;/opt/mail/rspamd-feedback&amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE = &amp;quot;/usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
HEADERS = (&amp;quot;From&amp;quot;, &amp;quot;To&amp;quot;, &amp;quot;Date&amp;quot;, &amp;quot;Subject&amp;quot;, &amp;quot;Message-ID&amp;quot;, &amp;quot;X-Spam-Status&amp;quot;, &amp;quot;X-Spam-Score&amp;quot;, &amp;quot;X-Rspamd-Action&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def emit(value):&lt;br /&gt;
    print(json.dumps(value, ensure_ascii=True, sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def require_root():&lt;br /&gt;
    if not hasattr(os, &amp;quot;geteuid&amp;quot;) or os.geteuid() != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;this administrative command requires root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_id(event_id):&lt;br /&gt;
    if not ID_RE.fullmatch(event_id):&lt;br /&gt;
        raise FeedbackError(&amp;quot;ID must be exactly 32 lowercase hexadecimal characters&amp;quot;)&lt;br /&gt;
    return event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Directory:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Nofollow directory descriptor on FreeBSD; portable fallback for local tests.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, path):&lt;br /&gt;
        self.path = Path(path)&lt;br /&gt;
        info = self.path.lstat()&lt;br /&gt;
        if not stat.S_ISDIR(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;expected a real directory&amp;quot;)&lt;br /&gt;
        self.fd = None&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            self.fd = os.open(self.path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
    def __enter__(self):&lt;br /&gt;
        return self&lt;br /&gt;
&lt;br /&gt;
    def __exit__(self, *args):&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
    def read(self, name, limit):&lt;br /&gt;
        if &amp;quot;/&amp;quot; in name or &amp;quot;\\&amp;quot; in name or name in (&amp;quot;.&amp;quot;, &amp;quot;..&amp;quot;):&lt;br /&gt;
            raise FeedbackError(&amp;quot;invalid internal filename&amp;quot;)&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=self.fd)&lt;br /&gt;
        else:&lt;br /&gt;
            path = self.path / name&lt;br /&gt;
            if path.is_symlink():&lt;br /&gt;
                raise FeedbackError(&amp;quot;symlink is not an event file&amp;quot;)&lt;br /&gt;
            fd = os.open(path, os.O_RDONLY | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0))&lt;br /&gt;
        with os.fdopen(fd, &amp;quot;rb&amp;quot;) as source:&lt;br /&gt;
            info = os.fstat(source.fileno())&lt;br /&gt;
            if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_size &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file is not a bounded, single-link regular file&amp;quot;)&lt;br /&gt;
            result = source.read(limit + 1)&lt;br /&gt;
            if len(result) &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file exceeded the size limit&amp;quot;)&lt;br /&gt;
            return result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def read_event(directory, event_id):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    # On POSIX open the event relative to the already opened parent descriptor.&lt;br /&gt;
    # The parent path is deployment-owned, and a symlink event is rejected.&lt;br /&gt;
    with Directory(Path(directory) / event_id) as entry:&lt;br /&gt;
        metadata = json.loads(entry.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
        message = entry.read(&amp;quot;message.eml&amp;quot;, MAX_MESSAGE)&lt;br /&gt;
    if not isinstance(metadata, dict) or not isinstance(metadata.get(&amp;quot;user&amp;quot;), str):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                             metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
    if metadata.get(&amp;quot;id&amp;quot;) != event_id or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    if metadata.get(&amp;quot;bytes&amp;quot;) != len(message) or metadata.get(&amp;quot;sha256&amp;quot;) != hashlib.sha256(message).hexdigest():&lt;br /&gt;
        raise FeedbackError(&amp;quot;event content does not match its metadata&amp;quot;)&lt;br /&gt;
    return metadata, message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def write_private(path, value, *, json_value=False):&lt;br /&gt;
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0)&lt;br /&gt;
    fd = os.open(path, flags, 0o600)&lt;br /&gt;
    with os.fdopen(fd, &amp;quot;wb&amp;quot;) as output:&lt;br /&gt;
        if json_value:&lt;br /&gt;
            value = (json.dumps(value, ensure_ascii=True, sort_keys=True) + &amp;quot;\n&amp;quot;).encode()&lt;br /&gt;
        output.write(value)&lt;br /&gt;
        output.flush()&lt;br /&gt;
        os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def update_receipt(directory, receipt):&lt;br /&gt;
    temporary = directory / &amp;quot;.admin-new.json&amp;quot;&lt;br /&gt;
    write_private(temporary, receipt, json_value=True)&lt;br /&gt;
    os.replace(temporary, directory / &amp;quot;admin.json&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_root_directory(path):&lt;br /&gt;
    with Directory(path):&lt;br /&gt;
        info = Path(path).stat()&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;administrative directories must be root-owned and not group/other writable&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def admin_lock(root):&lt;br /&gt;
    check_root_directory(root)&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        processed.mkdir(mode=0o700)&lt;br /&gt;
    check_root_directory(processed)&lt;br /&gt;
    fd = os.open(root / &amp;quot;.admin.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(fd, fcntl.LOCK_EX)&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def receipt_for(directory):&lt;br /&gt;
    with Directory(directory) as entry:&lt;br /&gt;
        return json.loads(entry.read(&amp;quot;admin.json&amp;quot;, 16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def find_prior(root, sha256):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return None&lt;br /&gt;
    for entry in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
            continue&lt;br /&gt;
        try:&lt;br /&gt;
            receipt = receipt_for(entry)&lt;br /&gt;
        except (OSError, ValueError, FeedbackError):&lt;br /&gt;
            # A damaged journal must never silently allow repeated training.&lt;br /&gt;
            raise FeedbackError(&amp;quot;processed journal is incomplete; inspect it before more training&amp;quot;)&lt;br /&gt;
        if receipt.get(&amp;quot;sha256&amp;quot;) == sha256 and receipt.get(&amp;quot;status&amp;quot;) in (&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learned&amp;quot;, &amp;quot;learn_uncertain&amp;quot;):&lt;br /&gt;
            return receipt&lt;br /&gt;
    return None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def prepare_archive(root, event_id, metadata, message, status, label=None, reason=None):&lt;br /&gt;
    if shutil.disk_usage(root).free &amp;lt; collector.MIN_FREE_BYTES + len(message) + 32768:&lt;br /&gt;
        raise FeedbackError(&amp;quot;insufficient free disk space to preserve the reviewed snapshot&amp;quot;)&lt;br /&gt;
    archive = root / &amp;quot;processed&amp;quot; / event_id&lt;br /&gt;
    archive.mkdir(mode=0o700)&lt;br /&gt;
    receipt = {&amp;quot;schema&amp;quot;: 1, &amp;quot;id&amp;quot;: event_id, &amp;quot;sha256&amp;quot;: metadata[&amp;quot;sha256&amp;quot;],&lt;br /&gt;
               &amp;quot;reviewed_utc&amp;quot;: collector.utc_now(), &amp;quot;status&amp;quot;: &amp;quot;preparing&amp;quot;,&lt;br /&gt;
               &amp;quot;selected_class&amp;quot;: label, &amp;quot;reason&amp;quot;: reason}&lt;br /&gt;
    write_private(archive / &amp;quot;admin.json&amp;quot;, receipt, json_value=True)&lt;br /&gt;
    write_private(archive / &amp;quot;message.eml&amp;quot;, message)&lt;br /&gt;
    write_private(archive / &amp;quot;metadata.json&amp;quot;, json.dumps(metadata, ensure_ascii=True).encode())&lt;br /&gt;
    # Keep the complete original event. Never traverse or execute files in source/.&lt;br /&gt;
    os.rename(root / &amp;quot;pending&amp;quot; / event_id, archive / &amp;quot;source&amp;quot;)&lt;br /&gt;
    receipt[&amp;quot;status&amp;quot;] = status&lt;br /&gt;
    update_receipt(archive, receipt)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;pending&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;processed&amp;quot;)&lt;br /&gt;
    return archive, receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def run_learning(message, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    executable = Path(executable)&lt;br /&gt;
    if not executable.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be an absolute path&amp;quot;)&lt;br /&gt;
    command = [str(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be an absolute regular file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be root-owned, mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        # rspamc reads the file itself; the secret never appears in argv or logs.&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;learn_&amp;quot; + label)&lt;br /&gt;
    # No -u/-d: one shared Bayes classifier. No shell and no automatic retry.&lt;br /&gt;
    return subprocess.run(command, input=message, stdout=subprocess.DEVNULL,&lt;br /&gt;
                          stderr=subprocess.DEVNULL, timeout=timeout, check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def learn_event(root, event_id, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid training class&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        prior = find_prior(root, metadata[&amp;quot;sha256&amp;quot;])&lt;br /&gt;
        if prior:&lt;br /&gt;
            raise FeedbackError(&amp;quot;this content has a prior learned or uncertain event; inspect it and reject the duplicate&amp;quot;)&lt;br /&gt;
        archive, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;learning&amp;quot;, label)&lt;br /&gt;
        try:&lt;br /&gt;
            rc = run_learning(message, label, executable=executable, controller=controller,&lt;br /&gt;
                              password_file=password_file, timeout=timeout)&lt;br /&gt;
        except subprocess.TimeoutExpired:&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client timeout; server may have learned; do not automatically retry&amp;quot;&lt;br /&gt;
        except (OSError, FeedbackError):&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_failed&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client could not start or local configuration was invalid&amp;quot;&lt;br /&gt;
        else:&lt;br /&gt;
            receipt[&amp;quot;returncode&amp;quot;] = rc&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if rc == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            if rc:&lt;br /&gt;
                receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; inspect server before any retry&amp;quot;&lt;br /&gt;
        receipt[&amp;quot;finished_utc&amp;quot;] = collector.utc_now()&lt;br /&gt;
        update_receipt(archive, receipt)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def reject_event(root, event_id, reason=&amp;quot;review rejected&amp;quot;):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if len(reason) &amp;gt; 500:&lt;br /&gt;
        raise FeedbackError(&amp;quot;reason is too long&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        _, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;rejected&amp;quot;, reason=reason)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def list_events(root, state, limit):&lt;br /&gt;
    count = 0&lt;br /&gt;
    for name in ((&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;) if state == &amp;quot;all&amp;quot; else (state,)):&lt;br /&gt;
        directory = root / name&lt;br /&gt;
        if not directory.exists():&lt;br /&gt;
            continue&lt;br /&gt;
        with Directory(directory):&lt;br /&gt;
            for entry in directory.iterdir():&lt;br /&gt;
                if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                if count &amp;gt;= limit:&lt;br /&gt;
                    emit({&amp;quot;notice&amp;quot;: &amp;quot;limit reached&amp;quot;, &amp;quot;limit&amp;quot;: limit})&lt;br /&gt;
                    return&lt;br /&gt;
                try:&lt;br /&gt;
                    with Directory(entry) as item:&lt;br /&gt;
                        metadata = json.loads(item.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    row = {key: metadata.get(key) for key in (&amp;quot;id&amp;quot;, &amp;quot;created_utc&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;user&amp;quot;, &amp;quot;source&amp;quot;, &amp;quot;destination&amp;quot;, &amp;quot;bytes&amp;quot;)}&lt;br /&gt;
                    row[&amp;quot;state&amp;quot;] = name&lt;br /&gt;
                    if name == &amp;quot;processed&amp;quot;:&lt;br /&gt;
                        row[&amp;quot;review&amp;quot;] = receipt_for(entry)&lt;br /&gt;
                    emit(row)&lt;br /&gt;
                except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                    emit({&amp;quot;id&amp;quot;: entry.name, &amp;quot;state&amp;quot;: name, &amp;quot;error&amp;quot;: &amp;quot;invalid event; manual inspection required&amp;quot;})&lt;br /&gt;
                count += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def show_event(root, event_id, state, path_only):&lt;br /&gt;
    metadata, message = read_event(root / state, check_id(event_id))&lt;br /&gt;
    if path_only:&lt;br /&gt;
        emit({&amp;quot;id&amp;quot;: event_id, &amp;quot;message_path&amp;quot;: str(root / state / event_id / &amp;quot;message.eml&amp;quot;)})&lt;br /&gt;
        return&lt;br /&gt;
    headers = BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message), 262144)])&lt;br /&gt;
    emit({&amp;quot;metadata&amp;quot;: metadata, &amp;quot;headers&amp;quot;: {name: [str(v)[:2000] for v in headers.get_all(name, [])[:10]] for name in HEADERS}})&lt;br /&gt;
    if state == &amp;quot;processed&amp;quot;:&lt;br /&gt;
        emit({&amp;quot;review&amp;quot;: receipt_for(root / state / event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def warn_processed_storage(root):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return&lt;br /&gt;
    total = count = 0&lt;br /&gt;
    for event in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(event.name) or event.is_symlink() or not event.is_dir():&lt;br /&gt;
            continue&lt;br /&gt;
        count += 1&lt;br /&gt;
        for prefix in (event, event / &amp;quot;source&amp;quot;):&lt;br /&gt;
            if prefix.is_symlink():&lt;br /&gt;
                continue&lt;br /&gt;
            for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    info = (prefix / name).lstat()&lt;br /&gt;
                    if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                        total += info.st_size&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    pass&lt;br /&gt;
    if total &amp;gt;= collector.MAX_PENDING_BYTES or count &amp;gt;= collector.MAX_PENDING_EVENTS:&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: WARNING processed archive reached 512 MiB or 1024 events; review retention manually; nothing deleted&amp;quot;, file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=DEFAULT_ROOT)&lt;br /&gt;
    sub = parser.add_subparsers(dest=&amp;quot;command&amp;quot;, required=True)&lt;br /&gt;
    listing = sub.add_parser(&amp;quot;list&amp;quot;, help=&amp;quot;JSON lines, metadata only&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;, &amp;quot;all&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--limit&amp;quot;, type=int, default=100)&lt;br /&gt;
    showing = sub.add_parser(&amp;quot;show&amp;quot;, help=&amp;quot;selected headers only; never dumps the body&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--path&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning = sub.add_parser(&amp;quot;learn&amp;quot;, help=&amp;quot;explicitly train one reviewed event, then archive it&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;label&amp;quot;, choices=(&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;))&lt;br /&gt;
    learning.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--password-file&amp;quot;, default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
                          help=&amp;quot;root-only plaintext secret file; use --no-password only for an intentionally trusted local controller&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--no-password&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    rejecting = sub.add_parser(&amp;quot;reject&amp;quot;, help=&amp;quot;archive one event without training or deleting it&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;--reason&amp;quot;, default=&amp;quot;review rejected&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    try:&lt;br /&gt;
        require_root()&lt;br /&gt;
        root = Path(args.queue_root)&lt;br /&gt;
        if not root.is_absolute():&lt;br /&gt;
            raise FeedbackError(&amp;quot;queue root must be absolute&amp;quot;)&lt;br /&gt;
        check_root_directory(root)&lt;br /&gt;
        warn_processed_storage(root)&lt;br /&gt;
        if args.command == &amp;quot;list&amp;quot;:&lt;br /&gt;
            if not 1 &amp;lt;= args.limit &amp;lt;= 10000:&lt;br /&gt;
                raise FeedbackError(&amp;quot;limit must be between 1 and 10000&amp;quot;)&lt;br /&gt;
            list_events(root, args.state, args.limit)&lt;br /&gt;
        elif args.command == &amp;quot;show&amp;quot;:&lt;br /&gt;
            show_event(root, args.id, args.state, args.path)&lt;br /&gt;
        elif args.command == &amp;quot;reject&amp;quot;:&lt;br /&gt;
            emit(reject_event(root, args.id, args.reason))&lt;br /&gt;
        else:&lt;br /&gt;
            if not 0 &amp;lt; args.timeout &amp;lt;= 300:&lt;br /&gt;
                raise FeedbackError(&amp;quot;timeout must be between 0 and 300 seconds&amp;quot;)&lt;br /&gt;
            result = learn_event(root, args.id, args.label, executable=args.rspamc_bin,&lt;br /&gt;
                                 controller=args.controller, password_file=None if args.no_password else args.password_file,&lt;br /&gt;
                                 timeout=args.timeout)&lt;br /&gt;
            emit(result)&lt;br /&gt;
            return 0 if result[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot; else 2&lt;br /&gt;
        return 0&lt;br /&gt;
    except (FeedbackError, OSError, ValueError, TypeError):&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: operation refused or failed; inspect configuration/event locally&amp;quot;, file=sys.stderr)&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    raise SystemExit(main())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only background learning from the bounded IMAPSieve feedback queue.&lt;br /&gt;
&lt;br /&gt;
The collector remains a local spool writer. This process owns controller&lt;br /&gt;
credentials, learns only validated INBOX/Junk events, preserves every original,&lt;br /&gt;
and never retries a training operation with an uncertain result. Run foreground&lt;br /&gt;
under FreeBSD daemon(8), or use --once for one bounded batch.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import signal&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
import threading&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
_spec = importlib.util.spec_from_file_location(&amp;quot;feedback_worker_admin&amp;quot;, Path(__file__).with_name(&amp;quot;feedback-admin.py&amp;quot;))&lt;br /&gt;
admin = importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError = admin.FeedbackError&lt;br /&gt;
SHA_RE = re.compile(r&amp;quot;[0-9a-f]{64}\Z&amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS = 20000&lt;br /&gt;
UNCERTAIN = {&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learn_uncertain&amp;quot;, &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;quarantined_ordering&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def log_event(kind, **fields):&lt;br /&gt;
    # Callers pass only fixed diagnostics, numeric counts, event IDs and classes.&lt;br /&gt;
    print(json.dumps({&amp;quot;event&amp;quot;: kind, **fields}, ensure_ascii=True, sort_keys=True), flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _timestamp_ns(value):&lt;br /&gt;
    if not isinstance(value, str):&lt;br /&gt;
        raise ValueError(&amp;quot;missing timestamp&amp;quot;)&lt;br /&gt;
    parsed = datetime.fromisoformat(value.replace(&amp;quot;Z&amp;quot;, &amp;quot;+00:00&amp;quot;))&lt;br /&gt;
    if parsed.tzinfo is None:&lt;br /&gt;
        raise ValueError(&amp;quot;timestamp must have timezone&amp;quot;)&lt;br /&gt;
    return int(parsed.timestamp() * 1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def order_events(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read bounded metadata, ordered by collector created_ns, then event ID.&lt;br /&gt;
&lt;br /&gt;
    Legacy second-resolution timestamps are permitted only when the same&lt;br /&gt;
    content has no contradictory labels at an identical timestamp. Such a tie&lt;br /&gt;
    cannot be resolved from random event IDs and is quarantined without learning.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    pending = Path(root) / &amp;quot;pending&amp;quot;&lt;br /&gt;
    rows = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    with admin.Directory(pending):&lt;br /&gt;
        with os.scandir(pending) as entries:&lt;br /&gt;
            for entry in entries:&lt;br /&gt;
                if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                count += 1&lt;br /&gt;
                if count &amp;gt; admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
                    raise FeedbackError(&amp;quot;pending queue scan bound exceeded&amp;quot;)&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(pending / entry.name) as directory:&lt;br /&gt;
                        metadata = json.loads(directory.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    if not isinstance(metadata, dict):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid metadata&amp;quot;)&lt;br /&gt;
                    metadata = dict(metadata)&lt;br /&gt;
                    if metadata.get(&amp;quot;id&amp;quot;) != entry.name or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid event identity&amp;quot;)&lt;br /&gt;
                    if not isinstance(metadata.get(&amp;quot;sha256&amp;quot;), str) or not SHA_RE.fullmatch(metadata[&amp;quot;sha256&amp;quot;]):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid digest&amp;quot;)&lt;br /&gt;
                    admin.collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                                                   metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
                    timestamp = metadata.get(&amp;quot;created_ns&amp;quot;)&lt;br /&gt;
                    if isinstance(timestamp, int) and not isinstance(timestamp, bool) and timestamp &amp;gt; 0:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = timestamp&lt;br /&gt;
                    elif &amp;quot;created_ns&amp;quot; not in metadata:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = _timestamp_ns(metadata.get(&amp;quot;created_utc&amp;quot;))&lt;br /&gt;
                    else:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = 0&lt;br /&gt;
                        metadata[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
                    rows.append(metadata)&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    # A manual administrator may have archived this candidate.&lt;br /&gt;
                    continue&lt;br /&gt;
                except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                    rows.append({&amp;quot;id&amp;quot;: entry.name, &amp;quot;_invalid&amp;quot;: True, &amp;quot;_order_ns&amp;quot;: 0})&lt;br /&gt;
    tied = {}&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
            tied.setdefault((row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;]), set()).add(row[&amp;quot;label&amp;quot;])&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;) and len(tied[(row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;])]) &amp;gt; 1:&lt;br /&gt;
            row[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
    return sorted(rows, key=lambda row: (row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;id&amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _journal(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read root-owned receipts; successful classes use durable worker sequence.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    processed = Path(root) / &amp;quot;processed&amp;quot;&lt;br /&gt;
    latest, blocked = {}, set()&lt;br /&gt;
    max_sequence = used_bytes = count = 0&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
    admin.check_root_directory(processed)&lt;br /&gt;
    with os.scandir(processed) as entries:&lt;br /&gt;
        for entry in entries:&lt;br /&gt;
            if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                continue&lt;br /&gt;
            count += 1&lt;br /&gt;
            if count &amp;gt; MAX_JOURNAL_EVENTS:&lt;br /&gt;
                raise FeedbackError(&amp;quot;processed journal scan bound exceeded&amp;quot;)&lt;br /&gt;
            directory = processed / entry.name&lt;br /&gt;
            receipt = admin.receipt_for(directory)&lt;br /&gt;
            if not isinstance(receipt, dict) or receipt.get(&amp;quot;id&amp;quot;) != entry.name:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed receipt&amp;quot;)&lt;br /&gt;
            digest = receipt.get(&amp;quot;sha256&amp;quot;)&lt;br /&gt;
            if not isinstance(digest, str) or not SHA_RE.fullmatch(digest):&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed digest&amp;quot;)&lt;br /&gt;
            sequence = receipt.get(&amp;quot;worker_sequence&amp;quot;, 0)&lt;br /&gt;
            if not isinstance(sequence, int) or isinstance(sequence, bool) or sequence &amp;lt; 0:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed sequence&amp;quot;)&lt;br /&gt;
            max_sequence = max(max_sequence, sequence)&lt;br /&gt;
            status = receipt.get(&amp;quot;status&amp;quot;)&lt;br /&gt;
            if status in UNCERTAIN:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
            if status == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                label = receipt.get(&amp;quot;selected_class&amp;quot;)&lt;br /&gt;
                if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
                    raise FeedbackError(&amp;quot;invalid learned class&amp;quot;)&lt;br /&gt;
                # Existing manual receipts predate automatic processing; the old&lt;br /&gt;
                # admin refuses further training of a SHA once it has a receipt.&lt;br /&gt;
                fallback = _timestamp_ns(receipt.get(&amp;quot;finished_utc&amp;quot;, receipt.get(&amp;quot;reviewed_utc&amp;quot;)))&lt;br /&gt;
                position = (bool(sequence), sequence if sequence else fallback, entry.name)&lt;br /&gt;
                if digest not in latest or position &amp;gt; latest[digest][0]:&lt;br /&gt;
                    latest[digest] = (position, label)&lt;br /&gt;
            # Count snapshots and preserved originals, never recursively walk&lt;br /&gt;
            # arbitrary files in the mail-user-owned source/ directory.&lt;br /&gt;
            for part in (directory, directory / &amp;quot;source&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(part) as safe:&lt;br /&gt;
                        for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                            try:&lt;br /&gt;
                                if safe.fd is not None:&lt;br /&gt;
                                    info = os.stat(name, dir_fd=safe.fd, follow_symlinks=False)&lt;br /&gt;
                                else:&lt;br /&gt;
                                    info = (part / name).lstat()&lt;br /&gt;
                                if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                                    used_bytes += info.st_size&lt;br /&gt;
                            except FileNotFoundError:&lt;br /&gt;
                                continue&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    continue&lt;br /&gt;
    return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _check_trusted_parent_chain(path):&lt;br /&gt;
    if os.name != &amp;quot;posix&amp;quot;:&lt;br /&gt;
        return&lt;br /&gt;
    for parent in Path(path).parents:&lt;br /&gt;
        link_info = parent.lstat()&lt;br /&gt;
        info = parent.stat()&lt;br /&gt;
        if (link_info.st_uid != 0 or not stat.S_ISDIR(info.st_mode)&lt;br /&gt;
                or info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;executable parent directories must be controlled by root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def resolve_executable(executable):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Allow FreeBSD package symlinks, but pin both operations to their target.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    source = Path(executable)&lt;br /&gt;
    if not source.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be absolute&amp;quot;)&lt;br /&gt;
    source_info = source.lstat()&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and source_info.st_uid != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc entry must be owned by root&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(source)&lt;br /&gt;
    target = source.resolve(strict=True)&lt;br /&gt;
    info = target.lstat()&lt;br /&gt;
    if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be a regular file&amp;quot;)&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be root-owned and not writable by mail users&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(target)&lt;br /&gt;
    return str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def controller_ready(executable, controller, password_file, timeout):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Authenticated read-only controller probe; no candidate is archived yet.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    command = [resolve_executable(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;unsafe controller password file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;controller password file must be root-owned mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;stat&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        return subprocess.run(command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,&lt;br /&gt;
                              stderr=subprocess.DEVNULL, check=False, timeout=min(timeout, 5)).returncode == 0&lt;br /&gt;
    except (subprocess.TimeoutExpired, OSError):&lt;br /&gt;
        return False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def process_pending(root, *, executable=&amp;quot;/usr/local/bin/rspamc&amp;quot;, controller=&amp;quot;127.0.0.1:11334&amp;quot;,&lt;br /&gt;
                    password_file=admin.DEFAULT_PASSWORD_FILE, timeout=30, batch_size=20,&lt;br /&gt;
                    preflight=None, learner=None, stop_event=None):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Process at most batch_size candidates. Inject probes/learner for unit tests.&lt;br /&gt;
&lt;br /&gt;
    One admin lock serializes manual and background operations. A crash after a&lt;br /&gt;
    durable learning intent leaves status=learning, which blocks this SHA on the&lt;br /&gt;
    next pass; subsequent candidates are preserved as quarantined_uncertain.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    root = Path(root)&lt;br /&gt;
    preflight = controller_ready if preflight is None else preflight&lt;br /&gt;
    if learner is None:&lt;br /&gt;
        # FreeBSD pkg installs rspamc as a root-owned versioned symlink. Resolve&lt;br /&gt;
        # it once so preflight and training invoke the same trusted executable.&lt;br /&gt;
        executable = resolve_executable(executable)&lt;br /&gt;
        learner = admin.run_learning&lt;br /&gt;
    summary = {&amp;quot;learned&amp;quot;: 0, &amp;quot;duplicates&amp;quot;: 0, &amp;quot;quarantined&amp;quot;: 0, &amp;quot;invalid&amp;quot;: 0,&lt;br /&gt;
               &amp;quot;pending&amp;quot;: 0, &amp;quot;controller_unavailable&amp;quot;: False, &amp;quot;storage_blocked&amp;quot;: False,&lt;br /&gt;
               &amp;quot;archive_warning&amp;quot;: False}&lt;br /&gt;
    with admin.admin_lock(root):&lt;br /&gt;
        events = order_events(root)&lt;br /&gt;
        summary[&amp;quot;pending&amp;quot;] = len(events)&lt;br /&gt;
        if not events:&lt;br /&gt;
            return summary&lt;br /&gt;
        latest, blocked, sequence, used_bytes, archive_count = _journal(root)&lt;br /&gt;
        summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
        if used_bytes &amp;gt;= admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
            summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        if not preflight(executable, controller, password_file, timeout):&lt;br /&gt;
            summary[&amp;quot;controller_unavailable&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        handled = 0&lt;br /&gt;
        for candidate in events:&lt;br /&gt;
            if handled &amp;gt;= batch_size or (stop_event is not None and stop_event.is_set()):&lt;br /&gt;
                break&lt;br /&gt;
            if candidate.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            event_id = candidate[&amp;quot;id&amp;quot;]&lt;br /&gt;
            if (root / &amp;quot;processed&amp;quot; / event_id).exists():&lt;br /&gt;
                # Crash before prepare_archive moved pending/ID: its root-owned&lt;br /&gt;
                # intent is authoritative. Preserve the pending original for&lt;br /&gt;
                # manual recovery and continue independent content, never retry.&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            try:&lt;br /&gt;
                metadata, message = admin.read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
            except FileNotFoundError:&lt;br /&gt;
                continue&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            # Do not act if the queued ordering/identity changed since enumeration.&lt;br /&gt;
            if any(metadata.get(key) != candidate.get(key) for key in&lt;br /&gt;
                   (&amp;quot;sha256&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;created_ns&amp;quot;, &amp;quot;created_utc&amp;quot;)):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            if used_bytes + 2 * len(message) + 32768 &amp;gt; admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            label, digest = metadata[&amp;quot;label&amp;quot;], metadata[&amp;quot;sha256&amp;quot;]&lt;br /&gt;
            if candidate.get(&amp;quot;_ordering_invalid&amp;quot;):&lt;br /&gt;
                status, reason = &amp;quot;quarantined_ordering&amp;quot;, &amp;quot;event ordering is ambiguous&amp;quot;&lt;br /&gt;
            elif digest in blocked:&lt;br /&gt;
                status, reason = &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;a prior learning operation has an uncertain result&amp;quot;&lt;br /&gt;
            elif digest in latest and latest[digest][1] == label:&lt;br /&gt;
                status, reason = &amp;quot;duplicate&amp;quot;, &amp;quot;same class as the latest successful learning operation&amp;quot;&lt;br /&gt;
            else:&lt;br /&gt;
                status, reason = &amp;quot;learning&amp;quot;, &amp;quot;automatic explicit IMAP feedback&amp;quot;&lt;br /&gt;
            try:&lt;br /&gt;
                archive, receipt = admin.prepare_archive(root, event_id, metadata, message, status, label, reason)&lt;br /&gt;
            except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                # prepare_archive writes a root journal before moving the source;&lt;br /&gt;
                # stop instead of retrying an incompletely prepared operation.&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            sequence += 1&lt;br /&gt;
            receipt.update({&amp;quot;mode&amp;quot;: &amp;quot;automatic_imap_feedback&amp;quot;, &amp;quot;worker_sequence&amp;quot;: sequence,&lt;br /&gt;
                            &amp;quot;processed_ns&amp;quot;: time.time_ns(), &amp;quot;source_created_ns&amp;quot;: candidate[&amp;quot;_order_ns&amp;quot;]})&lt;br /&gt;
            if digest in latest:&lt;br /&gt;
                receipt[&amp;quot;previous_successful_class&amp;quot;] = latest[digest][1]&lt;br /&gt;
            # Durable intent exists before the only call that may change Bayes.&lt;br /&gt;
            admin.update_receipt(archive, receipt)&lt;br /&gt;
            if status == &amp;quot;learning&amp;quot;:&lt;br /&gt;
                try:&lt;br /&gt;
                    returncode = learner(message, label, executable=executable, controller=controller,&lt;br /&gt;
                                         password_file=password_file, timeout=timeout)&lt;br /&gt;
                except (subprocess.TimeoutExpired, OSError, FeedbackError):&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    receipt[&amp;quot;error&amp;quot;] = &amp;quot;client failure; server may have learned; no automatic retry&amp;quot;&lt;br /&gt;
                else:&lt;br /&gt;
                    receipt[&amp;quot;returncode&amp;quot;] = returncode&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if returncode == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    if returncode != 0:&lt;br /&gt;
                        receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; no automatic retry&amp;quot;&lt;br /&gt;
                receipt[&amp;quot;finished_utc&amp;quot;] = admin.collector.utc_now()&lt;br /&gt;
                admin.update_receipt(archive, receipt)&lt;br /&gt;
                if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                    latest[digest] = ((True, sequence, event_id), label)&lt;br /&gt;
                    summary[&amp;quot;learned&amp;quot;] += 1&lt;br /&gt;
                else:&lt;br /&gt;
                    blocked.add(digest)&lt;br /&gt;
                    summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            elif status == &amp;quot;duplicate&amp;quot;:&lt;br /&gt;
                summary[&amp;quot;duplicates&amp;quot;] += 1&lt;br /&gt;
            else:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
                summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            handled += 1&lt;br /&gt;
            used_bytes += 2 * len(message) + 32768&lt;br /&gt;
            archive_count += 1&lt;br /&gt;
            summary[&amp;quot;pending&amp;quot;] -= 1&lt;br /&gt;
            summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
            log_event(&amp;quot;feedback_processed&amp;quot;, id=event_id, label=label, status=receipt[&amp;quot;status&amp;quot;])&lt;br /&gt;
            if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learn_uncertain&amp;quot;:&lt;br /&gt;
                # The controller may have failed after the preflight. Leave all&lt;br /&gt;
                # other candidates pending until a fresh read-only probe passes.&lt;br /&gt;
                break&lt;br /&gt;
    return summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=admin.DEFAULT_ROOT)&lt;br /&gt;
    parser.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--password-file&amp;quot;, default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
    parser.add_argument(&amp;quot;--poll&amp;quot;, type=float, default=2)&lt;br /&gt;
    parser.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    parser.add_argument(&amp;quot;--batch-size&amp;quot;, type=int, default=20)&lt;br /&gt;
    parser.add_argument(&amp;quot;--once&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    admin.require_root()&lt;br /&gt;
    root = Path(args.queue_root)&lt;br /&gt;
    if not root.is_absolute() or not 0.2 &amp;lt;= args.poll &amp;lt;= 60 or not 0 &amp;lt; args.timeout &amp;lt;= 300 or not 1 &amp;lt;= args.batch_size &amp;lt;= 100:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid worker options&amp;quot;)&lt;br /&gt;
    admin.check_root_directory(root)&lt;br /&gt;
    stopping = threading.Event()&lt;br /&gt;
    for signum in (signal.SIGTERM, signal.SIGINT):&lt;br /&gt;
        signal.signal(signum, lambda *_: stopping.set())&lt;br /&gt;
    lockfd = os.open(root / &amp;quot;.worker.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)&lt;br /&gt;
        previous_state = None&lt;br /&gt;
        while not stopping.is_set():&lt;br /&gt;
            try:&lt;br /&gt;
                summary = process_pending(root, executable=args.rspamc_bin, controller=args.controller,&lt;br /&gt;
                                          password_file=args.password_file, timeout=args.timeout,&lt;br /&gt;
                                          batch_size=args.batch_size, stop_event=stopping)&lt;br /&gt;
                state = {key: value for key, value in summary.items() if key not in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)}&lt;br /&gt;
                if state != previous_state or any(summary[key] for key in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)):&lt;br /&gt;
                    log_event(&amp;quot;worker_state&amp;quot;, **summary)&lt;br /&gt;
                previous_state = state&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 2 if summary[&amp;quot;controller_unavailable&amp;quot;] or summary[&amp;quot;storage_blocked&amp;quot;] else 0&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                if previous_state != &amp;quot;local_error&amp;quot;:&lt;br /&gt;
                    log_event(&amp;quot;worker_error&amp;quot;, reason=&amp;quot;local queue, journal or configuration failure; no automatic retry of uncertain training&amp;quot;)&lt;br /&gt;
                previous_state = &amp;quot;local_error&amp;quot;&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 1&lt;br /&gt;
            stopping.wait(args.poll)&lt;br /&gt;
        return 0&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    try:&lt;br /&gt;
        raise SystemExit(main())&lt;br /&gt;
    except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
        log_event(&amp;quot;worker_start_failed&amp;quot;, reason=&amp;quot;root, configuration or worker lock required&amp;quot;)&lt;br /&gt;
        raise SystemExit(1)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=689</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=689"/>
		<updated>2026-10-29T23:54:04Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 1. Схема и требования */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет → TCP 25 / STARTTLS → Exim&lt;br /&gt;
Клиент   → TCP 465 TLS или 587 STARTTLS + AUTH → Exim&lt;br /&gt;
Exim     → LMTP Unix socket → Dovecot → /opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim     → AUTH Unix socket → Dovecot → файловый каталог пользователей&lt;br /&gt;
Клиент   → TCP 993 TLS → Dovecot&lt;br /&gt;
SnappyMail (необязательно) → те же IMAPS и SMTP AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
sysrc hostname=mail.example.org&lt;br /&gt;
hostname mail.example.org&lt;br /&gt;
sysrc defaultrouter=198.51.100.1&lt;br /&gt;
# Только если в работающей таблице отсутствует default route:&lt;br /&gt;
route add default 198.51.100.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo /dev/da1 | awk &#039;{print $3}&#039;)&lt;br /&gt;
test -n &amp;quot;$disk_bytes&amp;quot; &amp;amp;&amp;amp; test &amp;quot;$disk_bytes&amp;quot; -gt 0 || exit 1&lt;br /&gt;
cmp -s -n &amp;quot;$disk_bytes&amp;quot; /dev/da1 /dev/zero&lt;br /&gt;
printf &#039;cmp status: %s\n&#039; &amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
pkg update&lt;br /&gt;
pkg install -n exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
# После проверки плана:&lt;br /&gt;
pkg install -y exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
sysrc exim_enable=NO dovecot_enable=NO&lt;br /&gt;
sysrc sendmail_enable=NONE sendmail_submit_enable=NO&lt;br /&gt;
sysrc sendmail_outbound_enable=NO sendmail_msp_queue_enable=NO&lt;br /&gt;
service sendmail onestop&lt;br /&gt;
/usr/local/sbin/exim -bV&lt;br /&gt;
/usr/local/sbin/dovecot --version&lt;br /&gt;
pw usershow mailnull&lt;br /&gt;
pw groupshow mail&lt;br /&gt;
pw groupshow dovecot&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only review and explicit training of IMAPSieve feedback.&lt;br /&gt;
&lt;br /&gt;
No command trains automatically. list/show are local reads. learn ID spam|ham&lt;br /&gt;
is the only command that invokes rspamc. reject ID archives without training.&lt;br /&gt;
Queue root and processed must be root-owned, non-writable by mail users;&lt;br /&gt;
pending is separately owned by vmail, mode 0700. Archived originals remain in&lt;br /&gt;
processed/ID/source; only root-owned snapshots are used for training or review.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from contextlib import contextmanager&lt;br /&gt;
from email import policy&lt;br /&gt;
from email.parser import BytesHeaderParser&lt;br /&gt;
import hashlib&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import shutil&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
&lt;br /&gt;
spec = importlib.util.spec_from_file_location(&amp;quot;feedback_collector&amp;quot;, Path(__file__).with_name(&amp;quot;feedback.py&amp;quot;))&lt;br /&gt;
collector = importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE = collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError = collector.FeedbackError&lt;br /&gt;
ID_RE = re.compile(r&amp;quot;[0-9a-f]{32}\Z&amp;quot;)&lt;br /&gt;
DEFAULT_ROOT = &amp;quot;/opt/mail/rspamd-feedback&amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE = &amp;quot;/usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
HEADERS = (&amp;quot;From&amp;quot;, &amp;quot;To&amp;quot;, &amp;quot;Date&amp;quot;, &amp;quot;Subject&amp;quot;, &amp;quot;Message-ID&amp;quot;, &amp;quot;X-Spam-Status&amp;quot;, &amp;quot;X-Spam-Score&amp;quot;, &amp;quot;X-Rspamd-Action&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def emit(value):&lt;br /&gt;
    print(json.dumps(value, ensure_ascii=True, sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def require_root():&lt;br /&gt;
    if not hasattr(os, &amp;quot;geteuid&amp;quot;) or os.geteuid() != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;this administrative command requires root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_id(event_id):&lt;br /&gt;
    if not ID_RE.fullmatch(event_id):&lt;br /&gt;
        raise FeedbackError(&amp;quot;ID must be exactly 32 lowercase hexadecimal characters&amp;quot;)&lt;br /&gt;
    return event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Directory:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Nofollow directory descriptor on FreeBSD; portable fallback for local tests.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, path):&lt;br /&gt;
        self.path = Path(path)&lt;br /&gt;
        info = self.path.lstat()&lt;br /&gt;
        if not stat.S_ISDIR(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;expected a real directory&amp;quot;)&lt;br /&gt;
        self.fd = None&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            self.fd = os.open(self.path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
    def __enter__(self):&lt;br /&gt;
        return self&lt;br /&gt;
&lt;br /&gt;
    def __exit__(self, *args):&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
    def read(self, name, limit):&lt;br /&gt;
        if &amp;quot;/&amp;quot; in name or &amp;quot;\\&amp;quot; in name or name in (&amp;quot;.&amp;quot;, &amp;quot;..&amp;quot;):&lt;br /&gt;
            raise FeedbackError(&amp;quot;invalid internal filename&amp;quot;)&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=self.fd)&lt;br /&gt;
        else:&lt;br /&gt;
            path = self.path / name&lt;br /&gt;
            if path.is_symlink():&lt;br /&gt;
                raise FeedbackError(&amp;quot;symlink is not an event file&amp;quot;)&lt;br /&gt;
            fd = os.open(path, os.O_RDONLY | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0))&lt;br /&gt;
        with os.fdopen(fd, &amp;quot;rb&amp;quot;) as source:&lt;br /&gt;
            info = os.fstat(source.fileno())&lt;br /&gt;
            if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_size &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file is not a bounded, single-link regular file&amp;quot;)&lt;br /&gt;
            result = source.read(limit + 1)&lt;br /&gt;
            if len(result) &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file exceeded the size limit&amp;quot;)&lt;br /&gt;
            return result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def read_event(directory, event_id):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    # On POSIX open the event relative to the already opened parent descriptor.&lt;br /&gt;
    # The parent path is deployment-owned, and a symlink event is rejected.&lt;br /&gt;
    with Directory(Path(directory) / event_id) as entry:&lt;br /&gt;
        metadata = json.loads(entry.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
        message = entry.read(&amp;quot;message.eml&amp;quot;, MAX_MESSAGE)&lt;br /&gt;
    if not isinstance(metadata, dict) or not isinstance(metadata.get(&amp;quot;user&amp;quot;), str):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                             metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
    if metadata.get(&amp;quot;id&amp;quot;) != event_id or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    if metadata.get(&amp;quot;bytes&amp;quot;) != len(message) or metadata.get(&amp;quot;sha256&amp;quot;) != hashlib.sha256(message).hexdigest():&lt;br /&gt;
        raise FeedbackError(&amp;quot;event content does not match its metadata&amp;quot;)&lt;br /&gt;
    return metadata, message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def write_private(path, value, *, json_value=False):&lt;br /&gt;
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0)&lt;br /&gt;
    fd = os.open(path, flags, 0o600)&lt;br /&gt;
    with os.fdopen(fd, &amp;quot;wb&amp;quot;) as output:&lt;br /&gt;
        if json_value:&lt;br /&gt;
            value = (json.dumps(value, ensure_ascii=True, sort_keys=True) + &amp;quot;\n&amp;quot;).encode()&lt;br /&gt;
        output.write(value)&lt;br /&gt;
        output.flush()&lt;br /&gt;
        os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def update_receipt(directory, receipt):&lt;br /&gt;
    temporary = directory / &amp;quot;.admin-new.json&amp;quot;&lt;br /&gt;
    write_private(temporary, receipt, json_value=True)&lt;br /&gt;
    os.replace(temporary, directory / &amp;quot;admin.json&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_root_directory(path):&lt;br /&gt;
    with Directory(path):&lt;br /&gt;
        info = Path(path).stat()&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;administrative directories must be root-owned and not group/other writable&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def admin_lock(root):&lt;br /&gt;
    check_root_directory(root)&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        processed.mkdir(mode=0o700)&lt;br /&gt;
    check_root_directory(processed)&lt;br /&gt;
    fd = os.open(root / &amp;quot;.admin.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(fd, fcntl.LOCK_EX)&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def receipt_for(directory):&lt;br /&gt;
    with Directory(directory) as entry:&lt;br /&gt;
        return json.loads(entry.read(&amp;quot;admin.json&amp;quot;, 16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def find_prior(root, sha256):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return None&lt;br /&gt;
    for entry in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
            continue&lt;br /&gt;
        try:&lt;br /&gt;
            receipt = receipt_for(entry)&lt;br /&gt;
        except (OSError, ValueError, FeedbackError):&lt;br /&gt;
            # A damaged journal must never silently allow repeated training.&lt;br /&gt;
            raise FeedbackError(&amp;quot;processed journal is incomplete; inspect it before more training&amp;quot;)&lt;br /&gt;
        if receipt.get(&amp;quot;sha256&amp;quot;) == sha256 and receipt.get(&amp;quot;status&amp;quot;) in (&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learned&amp;quot;, &amp;quot;learn_uncertain&amp;quot;):&lt;br /&gt;
            return receipt&lt;br /&gt;
    return None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def prepare_archive(root, event_id, metadata, message, status, label=None, reason=None):&lt;br /&gt;
    if shutil.disk_usage(root).free &amp;lt; collector.MIN_FREE_BYTES + len(message) + 32768:&lt;br /&gt;
        raise FeedbackError(&amp;quot;insufficient free disk space to preserve the reviewed snapshot&amp;quot;)&lt;br /&gt;
    archive = root / &amp;quot;processed&amp;quot; / event_id&lt;br /&gt;
    archive.mkdir(mode=0o700)&lt;br /&gt;
    receipt = {&amp;quot;schema&amp;quot;: 1, &amp;quot;id&amp;quot;: event_id, &amp;quot;sha256&amp;quot;: metadata[&amp;quot;sha256&amp;quot;],&lt;br /&gt;
               &amp;quot;reviewed_utc&amp;quot;: collector.utc_now(), &amp;quot;status&amp;quot;: &amp;quot;preparing&amp;quot;,&lt;br /&gt;
               &amp;quot;selected_class&amp;quot;: label, &amp;quot;reason&amp;quot;: reason}&lt;br /&gt;
    write_private(archive / &amp;quot;admin.json&amp;quot;, receipt, json_value=True)&lt;br /&gt;
    write_private(archive / &amp;quot;message.eml&amp;quot;, message)&lt;br /&gt;
    write_private(archive / &amp;quot;metadata.json&amp;quot;, json.dumps(metadata, ensure_ascii=True).encode())&lt;br /&gt;
    # Keep the complete original event. Never traverse or execute files in source/.&lt;br /&gt;
    os.rename(root / &amp;quot;pending&amp;quot; / event_id, archive / &amp;quot;source&amp;quot;)&lt;br /&gt;
    receipt[&amp;quot;status&amp;quot;] = status&lt;br /&gt;
    update_receipt(archive, receipt)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;pending&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;processed&amp;quot;)&lt;br /&gt;
    return archive, receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def run_learning(message, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    executable = Path(executable)&lt;br /&gt;
    if not executable.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be an absolute path&amp;quot;)&lt;br /&gt;
    command = [str(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be an absolute regular file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be root-owned, mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        # rspamc reads the file itself; the secret never appears in argv or logs.&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;learn_&amp;quot; + label)&lt;br /&gt;
    # No -u/-d: one shared Bayes classifier. No shell and no automatic retry.&lt;br /&gt;
    return subprocess.run(command, input=message, stdout=subprocess.DEVNULL,&lt;br /&gt;
                          stderr=subprocess.DEVNULL, timeout=timeout, check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def learn_event(root, event_id, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid training class&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        prior = find_prior(root, metadata[&amp;quot;sha256&amp;quot;])&lt;br /&gt;
        if prior:&lt;br /&gt;
            raise FeedbackError(&amp;quot;this content has a prior learned or uncertain event; inspect it and reject the duplicate&amp;quot;)&lt;br /&gt;
        archive, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;learning&amp;quot;, label)&lt;br /&gt;
        try:&lt;br /&gt;
            rc = run_learning(message, label, executable=executable, controller=controller,&lt;br /&gt;
                              password_file=password_file, timeout=timeout)&lt;br /&gt;
        except subprocess.TimeoutExpired:&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client timeout; server may have learned; do not automatically retry&amp;quot;&lt;br /&gt;
        except (OSError, FeedbackError):&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_failed&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client could not start or local configuration was invalid&amp;quot;&lt;br /&gt;
        else:&lt;br /&gt;
            receipt[&amp;quot;returncode&amp;quot;] = rc&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if rc == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            if rc:&lt;br /&gt;
                receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; inspect server before any retry&amp;quot;&lt;br /&gt;
        receipt[&amp;quot;finished_utc&amp;quot;] = collector.utc_now()&lt;br /&gt;
        update_receipt(archive, receipt)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def reject_event(root, event_id, reason=&amp;quot;review rejected&amp;quot;):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if len(reason) &amp;gt; 500:&lt;br /&gt;
        raise FeedbackError(&amp;quot;reason is too long&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        _, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;rejected&amp;quot;, reason=reason)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def list_events(root, state, limit):&lt;br /&gt;
    count = 0&lt;br /&gt;
    for name in ((&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;) if state == &amp;quot;all&amp;quot; else (state,)):&lt;br /&gt;
        directory = root / name&lt;br /&gt;
        if not directory.exists():&lt;br /&gt;
            continue&lt;br /&gt;
        with Directory(directory):&lt;br /&gt;
            for entry in directory.iterdir():&lt;br /&gt;
                if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                if count &amp;gt;= limit:&lt;br /&gt;
                    emit({&amp;quot;notice&amp;quot;: &amp;quot;limit reached&amp;quot;, &amp;quot;limit&amp;quot;: limit})&lt;br /&gt;
                    return&lt;br /&gt;
                try:&lt;br /&gt;
                    with Directory(entry) as item:&lt;br /&gt;
                        metadata = json.loads(item.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    row = {key: metadata.get(key) for key in (&amp;quot;id&amp;quot;, &amp;quot;created_utc&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;user&amp;quot;, &amp;quot;source&amp;quot;, &amp;quot;destination&amp;quot;, &amp;quot;bytes&amp;quot;)}&lt;br /&gt;
                    row[&amp;quot;state&amp;quot;] = name&lt;br /&gt;
                    if name == &amp;quot;processed&amp;quot;:&lt;br /&gt;
                        row[&amp;quot;review&amp;quot;] = receipt_for(entry)&lt;br /&gt;
                    emit(row)&lt;br /&gt;
                except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                    emit({&amp;quot;id&amp;quot;: entry.name, &amp;quot;state&amp;quot;: name, &amp;quot;error&amp;quot;: &amp;quot;invalid event; manual inspection required&amp;quot;})&lt;br /&gt;
                count += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def show_event(root, event_id, state, path_only):&lt;br /&gt;
    metadata, message = read_event(root / state, check_id(event_id))&lt;br /&gt;
    if path_only:&lt;br /&gt;
        emit({&amp;quot;id&amp;quot;: event_id, &amp;quot;message_path&amp;quot;: str(root / state / event_id / &amp;quot;message.eml&amp;quot;)})&lt;br /&gt;
        return&lt;br /&gt;
    headers = BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message), 262144)])&lt;br /&gt;
    emit({&amp;quot;metadata&amp;quot;: metadata, &amp;quot;headers&amp;quot;: {name: [str(v)[:2000] for v in headers.get_all(name, [])[:10]] for name in HEADERS}})&lt;br /&gt;
    if state == &amp;quot;processed&amp;quot;:&lt;br /&gt;
        emit({&amp;quot;review&amp;quot;: receipt_for(root / state / event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def warn_processed_storage(root):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return&lt;br /&gt;
    total = count = 0&lt;br /&gt;
    for event in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(event.name) or event.is_symlink() or not event.is_dir():&lt;br /&gt;
            continue&lt;br /&gt;
        count += 1&lt;br /&gt;
        for prefix in (event, event / &amp;quot;source&amp;quot;):&lt;br /&gt;
            if prefix.is_symlink():&lt;br /&gt;
                continue&lt;br /&gt;
            for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    info = (prefix / name).lstat()&lt;br /&gt;
                    if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                        total += info.st_size&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    pass&lt;br /&gt;
    if total &amp;gt;= collector.MAX_PENDING_BYTES or count &amp;gt;= collector.MAX_PENDING_EVENTS:&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: WARNING processed archive reached 512 MiB or 1024 events; review retention manually; nothing deleted&amp;quot;, file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=DEFAULT_ROOT)&lt;br /&gt;
    sub = parser.add_subparsers(dest=&amp;quot;command&amp;quot;, required=True)&lt;br /&gt;
    listing = sub.add_parser(&amp;quot;list&amp;quot;, help=&amp;quot;JSON lines, metadata only&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;, &amp;quot;all&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--limit&amp;quot;, type=int, default=100)&lt;br /&gt;
    showing = sub.add_parser(&amp;quot;show&amp;quot;, help=&amp;quot;selected headers only; never dumps the body&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--path&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning = sub.add_parser(&amp;quot;learn&amp;quot;, help=&amp;quot;explicitly train one reviewed event, then archive it&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;label&amp;quot;, choices=(&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;))&lt;br /&gt;
    learning.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--password-file&amp;quot;, default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
                          help=&amp;quot;root-only plaintext secret file; use --no-password only for an intentionally trusted local controller&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--no-password&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    rejecting = sub.add_parser(&amp;quot;reject&amp;quot;, help=&amp;quot;archive one event without training or deleting it&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;--reason&amp;quot;, default=&amp;quot;review rejected&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    try:&lt;br /&gt;
        require_root()&lt;br /&gt;
        root = Path(args.queue_root)&lt;br /&gt;
        if not root.is_absolute():&lt;br /&gt;
            raise FeedbackError(&amp;quot;queue root must be absolute&amp;quot;)&lt;br /&gt;
        check_root_directory(root)&lt;br /&gt;
        warn_processed_storage(root)&lt;br /&gt;
        if args.command == &amp;quot;list&amp;quot;:&lt;br /&gt;
            if not 1 &amp;lt;= args.limit &amp;lt;= 10000:&lt;br /&gt;
                raise FeedbackError(&amp;quot;limit must be between 1 and 10000&amp;quot;)&lt;br /&gt;
            list_events(root, args.state, args.limit)&lt;br /&gt;
        elif args.command == &amp;quot;show&amp;quot;:&lt;br /&gt;
            show_event(root, args.id, args.state, args.path)&lt;br /&gt;
        elif args.command == &amp;quot;reject&amp;quot;:&lt;br /&gt;
            emit(reject_event(root, args.id, args.reason))&lt;br /&gt;
        else:&lt;br /&gt;
            if not 0 &amp;lt; args.timeout &amp;lt;= 300:&lt;br /&gt;
                raise FeedbackError(&amp;quot;timeout must be between 0 and 300 seconds&amp;quot;)&lt;br /&gt;
            result = learn_event(root, args.id, args.label, executable=args.rspamc_bin,&lt;br /&gt;
                                 controller=args.controller, password_file=None if args.no_password else args.password_file,&lt;br /&gt;
                                 timeout=args.timeout)&lt;br /&gt;
            emit(result)&lt;br /&gt;
            return 0 if result[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot; else 2&lt;br /&gt;
        return 0&lt;br /&gt;
    except (FeedbackError, OSError, ValueError, TypeError):&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: operation refused or failed; inspect configuration/event locally&amp;quot;, file=sys.stderr)&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    raise SystemExit(main())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only background learning from the bounded IMAPSieve feedback queue.&lt;br /&gt;
&lt;br /&gt;
The collector remains a local spool writer. This process owns controller&lt;br /&gt;
credentials, learns only validated INBOX/Junk events, preserves every original,&lt;br /&gt;
and never retries a training operation with an uncertain result. Run foreground&lt;br /&gt;
under FreeBSD daemon(8), or use --once for one bounded batch.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import signal&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
import threading&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
_spec = importlib.util.spec_from_file_location(&amp;quot;feedback_worker_admin&amp;quot;, Path(__file__).with_name(&amp;quot;feedback-admin.py&amp;quot;))&lt;br /&gt;
admin = importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError = admin.FeedbackError&lt;br /&gt;
SHA_RE = re.compile(r&amp;quot;[0-9a-f]{64}\Z&amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS = 20000&lt;br /&gt;
UNCERTAIN = {&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learn_uncertain&amp;quot;, &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;quarantined_ordering&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def log_event(kind, **fields):&lt;br /&gt;
    # Callers pass only fixed diagnostics, numeric counts, event IDs and classes.&lt;br /&gt;
    print(json.dumps({&amp;quot;event&amp;quot;: kind, **fields}, ensure_ascii=True, sort_keys=True), flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _timestamp_ns(value):&lt;br /&gt;
    if not isinstance(value, str):&lt;br /&gt;
        raise ValueError(&amp;quot;missing timestamp&amp;quot;)&lt;br /&gt;
    parsed = datetime.fromisoformat(value.replace(&amp;quot;Z&amp;quot;, &amp;quot;+00:00&amp;quot;))&lt;br /&gt;
    if parsed.tzinfo is None:&lt;br /&gt;
        raise ValueError(&amp;quot;timestamp must have timezone&amp;quot;)&lt;br /&gt;
    return int(parsed.timestamp() * 1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def order_events(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read bounded metadata, ordered by collector created_ns, then event ID.&lt;br /&gt;
&lt;br /&gt;
    Legacy second-resolution timestamps are permitted only when the same&lt;br /&gt;
    content has no contradictory labels at an identical timestamp. Such a tie&lt;br /&gt;
    cannot be resolved from random event IDs and is quarantined without learning.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    pending = Path(root) / &amp;quot;pending&amp;quot;&lt;br /&gt;
    rows = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    with admin.Directory(pending):&lt;br /&gt;
        with os.scandir(pending) as entries:&lt;br /&gt;
            for entry in entries:&lt;br /&gt;
                if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                count += 1&lt;br /&gt;
                if count &amp;gt; admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
                    raise FeedbackError(&amp;quot;pending queue scan bound exceeded&amp;quot;)&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(pending / entry.name) as directory:&lt;br /&gt;
                        metadata = json.loads(directory.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    if not isinstance(metadata, dict):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid metadata&amp;quot;)&lt;br /&gt;
                    metadata = dict(metadata)&lt;br /&gt;
                    if metadata.get(&amp;quot;id&amp;quot;) != entry.name or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid event identity&amp;quot;)&lt;br /&gt;
                    if not isinstance(metadata.get(&amp;quot;sha256&amp;quot;), str) or not SHA_RE.fullmatch(metadata[&amp;quot;sha256&amp;quot;]):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid digest&amp;quot;)&lt;br /&gt;
                    admin.collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                                                   metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
                    timestamp = metadata.get(&amp;quot;created_ns&amp;quot;)&lt;br /&gt;
                    if isinstance(timestamp, int) and not isinstance(timestamp, bool) and timestamp &amp;gt; 0:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = timestamp&lt;br /&gt;
                    elif &amp;quot;created_ns&amp;quot; not in metadata:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = _timestamp_ns(metadata.get(&amp;quot;created_utc&amp;quot;))&lt;br /&gt;
                    else:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = 0&lt;br /&gt;
                        metadata[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
                    rows.append(metadata)&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    # A manual administrator may have archived this candidate.&lt;br /&gt;
                    continue&lt;br /&gt;
                except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                    rows.append({&amp;quot;id&amp;quot;: entry.name, &amp;quot;_invalid&amp;quot;: True, &amp;quot;_order_ns&amp;quot;: 0})&lt;br /&gt;
    tied = {}&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
            tied.setdefault((row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;]), set()).add(row[&amp;quot;label&amp;quot;])&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;) and len(tied[(row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;])]) &amp;gt; 1:&lt;br /&gt;
            row[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
    return sorted(rows, key=lambda row: (row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;id&amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _journal(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read root-owned receipts; successful classes use durable worker sequence.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    processed = Path(root) / &amp;quot;processed&amp;quot;&lt;br /&gt;
    latest, blocked = {}, set()&lt;br /&gt;
    max_sequence = used_bytes = count = 0&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
    admin.check_root_directory(processed)&lt;br /&gt;
    with os.scandir(processed) as entries:&lt;br /&gt;
        for entry in entries:&lt;br /&gt;
            if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                continue&lt;br /&gt;
            count += 1&lt;br /&gt;
            if count &amp;gt; MAX_JOURNAL_EVENTS:&lt;br /&gt;
                raise FeedbackError(&amp;quot;processed journal scan bound exceeded&amp;quot;)&lt;br /&gt;
            directory = processed / entry.name&lt;br /&gt;
            receipt = admin.receipt_for(directory)&lt;br /&gt;
            if not isinstance(receipt, dict) or receipt.get(&amp;quot;id&amp;quot;) != entry.name:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed receipt&amp;quot;)&lt;br /&gt;
            digest = receipt.get(&amp;quot;sha256&amp;quot;)&lt;br /&gt;
            if not isinstance(digest, str) or not SHA_RE.fullmatch(digest):&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed digest&amp;quot;)&lt;br /&gt;
            sequence = receipt.get(&amp;quot;worker_sequence&amp;quot;, 0)&lt;br /&gt;
            if not isinstance(sequence, int) or isinstance(sequence, bool) or sequence &amp;lt; 0:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed sequence&amp;quot;)&lt;br /&gt;
            max_sequence = max(max_sequence, sequence)&lt;br /&gt;
            status = receipt.get(&amp;quot;status&amp;quot;)&lt;br /&gt;
            if status in UNCERTAIN:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
            if status == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                label = receipt.get(&amp;quot;selected_class&amp;quot;)&lt;br /&gt;
                if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
                    raise FeedbackError(&amp;quot;invalid learned class&amp;quot;)&lt;br /&gt;
                # Existing manual receipts predate automatic processing; the old&lt;br /&gt;
                # admin refuses further training of a SHA once it has a receipt.&lt;br /&gt;
                fallback = _timestamp_ns(receipt.get(&amp;quot;finished_utc&amp;quot;, receipt.get(&amp;quot;reviewed_utc&amp;quot;)))&lt;br /&gt;
                position = (bool(sequence), sequence if sequence else fallback, entry.name)&lt;br /&gt;
                if digest not in latest or position &amp;gt; latest[digest][0]:&lt;br /&gt;
                    latest[digest] = (position, label)&lt;br /&gt;
            # Count snapshots and preserved originals, never recursively walk&lt;br /&gt;
            # arbitrary files in the mail-user-owned source/ directory.&lt;br /&gt;
            for part in (directory, directory / &amp;quot;source&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(part) as safe:&lt;br /&gt;
                        for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                            try:&lt;br /&gt;
                                if safe.fd is not None:&lt;br /&gt;
                                    info = os.stat(name, dir_fd=safe.fd, follow_symlinks=False)&lt;br /&gt;
                                else:&lt;br /&gt;
                                    info = (part / name).lstat()&lt;br /&gt;
                                if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                                    used_bytes += info.st_size&lt;br /&gt;
                            except FileNotFoundError:&lt;br /&gt;
                                continue&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    continue&lt;br /&gt;
    return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _check_trusted_parent_chain(path):&lt;br /&gt;
    if os.name != &amp;quot;posix&amp;quot;:&lt;br /&gt;
        return&lt;br /&gt;
    for parent in Path(path).parents:&lt;br /&gt;
        link_info = parent.lstat()&lt;br /&gt;
        info = parent.stat()&lt;br /&gt;
        if (link_info.st_uid != 0 or not stat.S_ISDIR(info.st_mode)&lt;br /&gt;
                or info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;executable parent directories must be controlled by root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def resolve_executable(executable):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Allow FreeBSD package symlinks, but pin both operations to their target.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    source = Path(executable)&lt;br /&gt;
    if not source.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be absolute&amp;quot;)&lt;br /&gt;
    source_info = source.lstat()&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and source_info.st_uid != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc entry must be owned by root&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(source)&lt;br /&gt;
    target = source.resolve(strict=True)&lt;br /&gt;
    info = target.lstat()&lt;br /&gt;
    if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be a regular file&amp;quot;)&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be root-owned and not writable by mail users&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(target)&lt;br /&gt;
    return str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def controller_ready(executable, controller, password_file, timeout):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Authenticated read-only controller probe; no candidate is archived yet.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    command = [resolve_executable(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;unsafe controller password file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;controller password file must be root-owned mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;stat&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        return subprocess.run(command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,&lt;br /&gt;
                              stderr=subprocess.DEVNULL, check=False, timeout=min(timeout, 5)).returncode == 0&lt;br /&gt;
    except (subprocess.TimeoutExpired, OSError):&lt;br /&gt;
        return False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def process_pending(root, *, executable=&amp;quot;/usr/local/bin/rspamc&amp;quot;, controller=&amp;quot;127.0.0.1:11334&amp;quot;,&lt;br /&gt;
                    password_file=admin.DEFAULT_PASSWORD_FILE, timeout=30, batch_size=20,&lt;br /&gt;
                    preflight=None, learner=None, stop_event=None):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Process at most batch_size candidates. Inject probes/learner for unit tests.&lt;br /&gt;
&lt;br /&gt;
    One admin lock serializes manual and background operations. A crash after a&lt;br /&gt;
    durable learning intent leaves status=learning, which blocks this SHA on the&lt;br /&gt;
    next pass; subsequent candidates are preserved as quarantined_uncertain.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    root = Path(root)&lt;br /&gt;
    preflight = controller_ready if preflight is None else preflight&lt;br /&gt;
    if learner is None:&lt;br /&gt;
        # FreeBSD pkg installs rspamc as a root-owned versioned symlink. Resolve&lt;br /&gt;
        # it once so preflight and training invoke the same trusted executable.&lt;br /&gt;
        executable = resolve_executable(executable)&lt;br /&gt;
        learner = admin.run_learning&lt;br /&gt;
    summary = {&amp;quot;learned&amp;quot;: 0, &amp;quot;duplicates&amp;quot;: 0, &amp;quot;quarantined&amp;quot;: 0, &amp;quot;invalid&amp;quot;: 0,&lt;br /&gt;
               &amp;quot;pending&amp;quot;: 0, &amp;quot;controller_unavailable&amp;quot;: False, &amp;quot;storage_blocked&amp;quot;: False,&lt;br /&gt;
               &amp;quot;archive_warning&amp;quot;: False}&lt;br /&gt;
    with admin.admin_lock(root):&lt;br /&gt;
        events = order_events(root)&lt;br /&gt;
        summary[&amp;quot;pending&amp;quot;] = len(events)&lt;br /&gt;
        if not events:&lt;br /&gt;
            return summary&lt;br /&gt;
        latest, blocked, sequence, used_bytes, archive_count = _journal(root)&lt;br /&gt;
        summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
        if used_bytes &amp;gt;= admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
            summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        if not preflight(executable, controller, password_file, timeout):&lt;br /&gt;
            summary[&amp;quot;controller_unavailable&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        handled = 0&lt;br /&gt;
        for candidate in events:&lt;br /&gt;
            if handled &amp;gt;= batch_size or (stop_event is not None and stop_event.is_set()):&lt;br /&gt;
                break&lt;br /&gt;
            if candidate.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            event_id = candidate[&amp;quot;id&amp;quot;]&lt;br /&gt;
            if (root / &amp;quot;processed&amp;quot; / event_id).exists():&lt;br /&gt;
                # Crash before prepare_archive moved pending/ID: its root-owned&lt;br /&gt;
                # intent is authoritative. Preserve the pending original for&lt;br /&gt;
                # manual recovery and continue independent content, never retry.&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            try:&lt;br /&gt;
                metadata, message = admin.read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
            except FileNotFoundError:&lt;br /&gt;
                continue&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            # Do not act if the queued ordering/identity changed since enumeration.&lt;br /&gt;
            if any(metadata.get(key) != candidate.get(key) for key in&lt;br /&gt;
                   (&amp;quot;sha256&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;created_ns&amp;quot;, &amp;quot;created_utc&amp;quot;)):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            if used_bytes + 2 * len(message) + 32768 &amp;gt; admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            label, digest = metadata[&amp;quot;label&amp;quot;], metadata[&amp;quot;sha256&amp;quot;]&lt;br /&gt;
            if candidate.get(&amp;quot;_ordering_invalid&amp;quot;):&lt;br /&gt;
                status, reason = &amp;quot;quarantined_ordering&amp;quot;, &amp;quot;event ordering is ambiguous&amp;quot;&lt;br /&gt;
            elif digest in blocked:&lt;br /&gt;
                status, reason = &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;a prior learning operation has an uncertain result&amp;quot;&lt;br /&gt;
            elif digest in latest and latest[digest][1] == label:&lt;br /&gt;
                status, reason = &amp;quot;duplicate&amp;quot;, &amp;quot;same class as the latest successful learning operation&amp;quot;&lt;br /&gt;
            else:&lt;br /&gt;
                status, reason = &amp;quot;learning&amp;quot;, &amp;quot;automatic explicit IMAP feedback&amp;quot;&lt;br /&gt;
            try:&lt;br /&gt;
                archive, receipt = admin.prepare_archive(root, event_id, metadata, message, status, label, reason)&lt;br /&gt;
            except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                # prepare_archive writes a root journal before moving the source;&lt;br /&gt;
                # stop instead of retrying an incompletely prepared operation.&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            sequence += 1&lt;br /&gt;
            receipt.update({&amp;quot;mode&amp;quot;: &amp;quot;automatic_imap_feedback&amp;quot;, &amp;quot;worker_sequence&amp;quot;: sequence,&lt;br /&gt;
                            &amp;quot;processed_ns&amp;quot;: time.time_ns(), &amp;quot;source_created_ns&amp;quot;: candidate[&amp;quot;_order_ns&amp;quot;]})&lt;br /&gt;
            if digest in latest:&lt;br /&gt;
                receipt[&amp;quot;previous_successful_class&amp;quot;] = latest[digest][1]&lt;br /&gt;
            # Durable intent exists before the only call that may change Bayes.&lt;br /&gt;
            admin.update_receipt(archive, receipt)&lt;br /&gt;
            if status == &amp;quot;learning&amp;quot;:&lt;br /&gt;
                try:&lt;br /&gt;
                    returncode = learner(message, label, executable=executable, controller=controller,&lt;br /&gt;
                                         password_file=password_file, timeout=timeout)&lt;br /&gt;
                except (subprocess.TimeoutExpired, OSError, FeedbackError):&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    receipt[&amp;quot;error&amp;quot;] = &amp;quot;client failure; server may have learned; no automatic retry&amp;quot;&lt;br /&gt;
                else:&lt;br /&gt;
                    receipt[&amp;quot;returncode&amp;quot;] = returncode&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if returncode == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    if returncode != 0:&lt;br /&gt;
                        receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; no automatic retry&amp;quot;&lt;br /&gt;
                receipt[&amp;quot;finished_utc&amp;quot;] = admin.collector.utc_now()&lt;br /&gt;
                admin.update_receipt(archive, receipt)&lt;br /&gt;
                if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                    latest[digest] = ((True, sequence, event_id), label)&lt;br /&gt;
                    summary[&amp;quot;learned&amp;quot;] += 1&lt;br /&gt;
                else:&lt;br /&gt;
                    blocked.add(digest)&lt;br /&gt;
                    summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            elif status == &amp;quot;duplicate&amp;quot;:&lt;br /&gt;
                summary[&amp;quot;duplicates&amp;quot;] += 1&lt;br /&gt;
            else:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
                summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            handled += 1&lt;br /&gt;
            used_bytes += 2 * len(message) + 32768&lt;br /&gt;
            archive_count += 1&lt;br /&gt;
            summary[&amp;quot;pending&amp;quot;] -= 1&lt;br /&gt;
            summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
            log_event(&amp;quot;feedback_processed&amp;quot;, id=event_id, label=label, status=receipt[&amp;quot;status&amp;quot;])&lt;br /&gt;
            if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learn_uncertain&amp;quot;:&lt;br /&gt;
                # The controller may have failed after the preflight. Leave all&lt;br /&gt;
                # other candidates pending until a fresh read-only probe passes.&lt;br /&gt;
                break&lt;br /&gt;
    return summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=admin.DEFAULT_ROOT)&lt;br /&gt;
    parser.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--password-file&amp;quot;, default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
    parser.add_argument(&amp;quot;--poll&amp;quot;, type=float, default=2)&lt;br /&gt;
    parser.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    parser.add_argument(&amp;quot;--batch-size&amp;quot;, type=int, default=20)&lt;br /&gt;
    parser.add_argument(&amp;quot;--once&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    admin.require_root()&lt;br /&gt;
    root = Path(args.queue_root)&lt;br /&gt;
    if not root.is_absolute() or not 0.2 &amp;lt;= args.poll &amp;lt;= 60 or not 0 &amp;lt; args.timeout &amp;lt;= 300 or not 1 &amp;lt;= args.batch_size &amp;lt;= 100:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid worker options&amp;quot;)&lt;br /&gt;
    admin.check_root_directory(root)&lt;br /&gt;
    stopping = threading.Event()&lt;br /&gt;
    for signum in (signal.SIGTERM, signal.SIGINT):&lt;br /&gt;
        signal.signal(signum, lambda *_: stopping.set())&lt;br /&gt;
    lockfd = os.open(root / &amp;quot;.worker.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)&lt;br /&gt;
        previous_state = None&lt;br /&gt;
        while not stopping.is_set():&lt;br /&gt;
            try:&lt;br /&gt;
                summary = process_pending(root, executable=args.rspamc_bin, controller=args.controller,&lt;br /&gt;
                                          password_file=args.password_file, timeout=args.timeout,&lt;br /&gt;
                                          batch_size=args.batch_size, stop_event=stopping)&lt;br /&gt;
                state = {key: value for key, value in summary.items() if key not in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)}&lt;br /&gt;
                if state != previous_state or any(summary[key] for key in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)):&lt;br /&gt;
                    log_event(&amp;quot;worker_state&amp;quot;, **summary)&lt;br /&gt;
                previous_state = state&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 2 if summary[&amp;quot;controller_unavailable&amp;quot;] or summary[&amp;quot;storage_blocked&amp;quot;] else 0&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                if previous_state != &amp;quot;local_error&amp;quot;:&lt;br /&gt;
                    log_event(&amp;quot;worker_error&amp;quot;, reason=&amp;quot;local queue, journal or configuration failure; no automatic retry of uncertain training&amp;quot;)&lt;br /&gt;
                previous_state = &amp;quot;local_error&amp;quot;&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 1&lt;br /&gt;
            stopping.wait(args.poll)&lt;br /&gt;
        return 0&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    try:&lt;br /&gt;
        raise SystemExit(main())&lt;br /&gt;
    except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
        log_event(&amp;quot;worker_start_failed&amp;quot;, reason=&amp;quot;root, configuration or worker lock required&amp;quot;)&lt;br /&gt;
        raise SystemExit(1)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=688</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=688"/>
		<updated>2026-10-29T23:53:26Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 1. Схема и требования */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет → TCP 25 / STARTTLS → Exim&lt;br /&gt;
Клиент   → TCP 465 TLS или 587 STARTTLS + AUTH → Exim&lt;br /&gt;
Exim     → LMTP Unix socket → Dovecot → /opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim     → AUTH Unix socket → Dovecot → файловый каталог пользователей&lt;br /&gt;
Клиент   → TCP 993 TLS → Dovecot&lt;br /&gt;
SnappyMail (необязательно) → те же IMAPS и SMTP AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:img1.jpg|400px]]&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
sysrc hostname=mail.example.org&lt;br /&gt;
hostname mail.example.org&lt;br /&gt;
sysrc defaultrouter=198.51.100.1&lt;br /&gt;
# Только если в работающей таблице отсутствует default route:&lt;br /&gt;
route add default 198.51.100.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo /dev/da1 | awk &#039;{print $3}&#039;)&lt;br /&gt;
test -n &amp;quot;$disk_bytes&amp;quot; &amp;amp;&amp;amp; test &amp;quot;$disk_bytes&amp;quot; -gt 0 || exit 1&lt;br /&gt;
cmp -s -n &amp;quot;$disk_bytes&amp;quot; /dev/da1 /dev/zero&lt;br /&gt;
printf &#039;cmp status: %s\n&#039; &amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
pkg update&lt;br /&gt;
pkg install -n exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
# После проверки плана:&lt;br /&gt;
pkg install -y exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
sysrc exim_enable=NO dovecot_enable=NO&lt;br /&gt;
sysrc sendmail_enable=NONE sendmail_submit_enable=NO&lt;br /&gt;
sysrc sendmail_outbound_enable=NO sendmail_msp_queue_enable=NO&lt;br /&gt;
service sendmail onestop&lt;br /&gt;
/usr/local/sbin/exim -bV&lt;br /&gt;
/usr/local/sbin/dovecot --version&lt;br /&gt;
pw usershow mailnull&lt;br /&gt;
pw groupshow mail&lt;br /&gt;
pw groupshow dovecot&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only review and explicit training of IMAPSieve feedback.&lt;br /&gt;
&lt;br /&gt;
No command trains automatically. list/show are local reads. learn ID spam|ham&lt;br /&gt;
is the only command that invokes rspamc. reject ID archives without training.&lt;br /&gt;
Queue root and processed must be root-owned, non-writable by mail users;&lt;br /&gt;
pending is separately owned by vmail, mode 0700. Archived originals remain in&lt;br /&gt;
processed/ID/source; only root-owned snapshots are used for training or review.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from contextlib import contextmanager&lt;br /&gt;
from email import policy&lt;br /&gt;
from email.parser import BytesHeaderParser&lt;br /&gt;
import hashlib&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import shutil&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
&lt;br /&gt;
spec = importlib.util.spec_from_file_location(&amp;quot;feedback_collector&amp;quot;, Path(__file__).with_name(&amp;quot;feedback.py&amp;quot;))&lt;br /&gt;
collector = importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE = collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError = collector.FeedbackError&lt;br /&gt;
ID_RE = re.compile(r&amp;quot;[0-9a-f]{32}\Z&amp;quot;)&lt;br /&gt;
DEFAULT_ROOT = &amp;quot;/opt/mail/rspamd-feedback&amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE = &amp;quot;/usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
HEADERS = (&amp;quot;From&amp;quot;, &amp;quot;To&amp;quot;, &amp;quot;Date&amp;quot;, &amp;quot;Subject&amp;quot;, &amp;quot;Message-ID&amp;quot;, &amp;quot;X-Spam-Status&amp;quot;, &amp;quot;X-Spam-Score&amp;quot;, &amp;quot;X-Rspamd-Action&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def emit(value):&lt;br /&gt;
    print(json.dumps(value, ensure_ascii=True, sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def require_root():&lt;br /&gt;
    if not hasattr(os, &amp;quot;geteuid&amp;quot;) or os.geteuid() != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;this administrative command requires root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_id(event_id):&lt;br /&gt;
    if not ID_RE.fullmatch(event_id):&lt;br /&gt;
        raise FeedbackError(&amp;quot;ID must be exactly 32 lowercase hexadecimal characters&amp;quot;)&lt;br /&gt;
    return event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Directory:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Nofollow directory descriptor on FreeBSD; portable fallback for local tests.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, path):&lt;br /&gt;
        self.path = Path(path)&lt;br /&gt;
        info = self.path.lstat()&lt;br /&gt;
        if not stat.S_ISDIR(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;expected a real directory&amp;quot;)&lt;br /&gt;
        self.fd = None&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            self.fd = os.open(self.path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
    def __enter__(self):&lt;br /&gt;
        return self&lt;br /&gt;
&lt;br /&gt;
    def __exit__(self, *args):&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
    def read(self, name, limit):&lt;br /&gt;
        if &amp;quot;/&amp;quot; in name or &amp;quot;\\&amp;quot; in name or name in (&amp;quot;.&amp;quot;, &amp;quot;..&amp;quot;):&lt;br /&gt;
            raise FeedbackError(&amp;quot;invalid internal filename&amp;quot;)&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=self.fd)&lt;br /&gt;
        else:&lt;br /&gt;
            path = self.path / name&lt;br /&gt;
            if path.is_symlink():&lt;br /&gt;
                raise FeedbackError(&amp;quot;symlink is not an event file&amp;quot;)&lt;br /&gt;
            fd = os.open(path, os.O_RDONLY | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0))&lt;br /&gt;
        with os.fdopen(fd, &amp;quot;rb&amp;quot;) as source:&lt;br /&gt;
            info = os.fstat(source.fileno())&lt;br /&gt;
            if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_size &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file is not a bounded, single-link regular file&amp;quot;)&lt;br /&gt;
            result = source.read(limit + 1)&lt;br /&gt;
            if len(result) &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file exceeded the size limit&amp;quot;)&lt;br /&gt;
            return result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def read_event(directory, event_id):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    # On POSIX open the event relative to the already opened parent descriptor.&lt;br /&gt;
    # The parent path is deployment-owned, and a symlink event is rejected.&lt;br /&gt;
    with Directory(Path(directory) / event_id) as entry:&lt;br /&gt;
        metadata = json.loads(entry.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
        message = entry.read(&amp;quot;message.eml&amp;quot;, MAX_MESSAGE)&lt;br /&gt;
    if not isinstance(metadata, dict) or not isinstance(metadata.get(&amp;quot;user&amp;quot;), str):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                             metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
    if metadata.get(&amp;quot;id&amp;quot;) != event_id or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    if metadata.get(&amp;quot;bytes&amp;quot;) != len(message) or metadata.get(&amp;quot;sha256&amp;quot;) != hashlib.sha256(message).hexdigest():&lt;br /&gt;
        raise FeedbackError(&amp;quot;event content does not match its metadata&amp;quot;)&lt;br /&gt;
    return metadata, message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def write_private(path, value, *, json_value=False):&lt;br /&gt;
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0)&lt;br /&gt;
    fd = os.open(path, flags, 0o600)&lt;br /&gt;
    with os.fdopen(fd, &amp;quot;wb&amp;quot;) as output:&lt;br /&gt;
        if json_value:&lt;br /&gt;
            value = (json.dumps(value, ensure_ascii=True, sort_keys=True) + &amp;quot;\n&amp;quot;).encode()&lt;br /&gt;
        output.write(value)&lt;br /&gt;
        output.flush()&lt;br /&gt;
        os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def update_receipt(directory, receipt):&lt;br /&gt;
    temporary = directory / &amp;quot;.admin-new.json&amp;quot;&lt;br /&gt;
    write_private(temporary, receipt, json_value=True)&lt;br /&gt;
    os.replace(temporary, directory / &amp;quot;admin.json&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_root_directory(path):&lt;br /&gt;
    with Directory(path):&lt;br /&gt;
        info = Path(path).stat()&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;administrative directories must be root-owned and not group/other writable&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def admin_lock(root):&lt;br /&gt;
    check_root_directory(root)&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        processed.mkdir(mode=0o700)&lt;br /&gt;
    check_root_directory(processed)&lt;br /&gt;
    fd = os.open(root / &amp;quot;.admin.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(fd, fcntl.LOCK_EX)&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def receipt_for(directory):&lt;br /&gt;
    with Directory(directory) as entry:&lt;br /&gt;
        return json.loads(entry.read(&amp;quot;admin.json&amp;quot;, 16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def find_prior(root, sha256):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return None&lt;br /&gt;
    for entry in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
            continue&lt;br /&gt;
        try:&lt;br /&gt;
            receipt = receipt_for(entry)&lt;br /&gt;
        except (OSError, ValueError, FeedbackError):&lt;br /&gt;
            # A damaged journal must never silently allow repeated training.&lt;br /&gt;
            raise FeedbackError(&amp;quot;processed journal is incomplete; inspect it before more training&amp;quot;)&lt;br /&gt;
        if receipt.get(&amp;quot;sha256&amp;quot;) == sha256 and receipt.get(&amp;quot;status&amp;quot;) in (&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learned&amp;quot;, &amp;quot;learn_uncertain&amp;quot;):&lt;br /&gt;
            return receipt&lt;br /&gt;
    return None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def prepare_archive(root, event_id, metadata, message, status, label=None, reason=None):&lt;br /&gt;
    if shutil.disk_usage(root).free &amp;lt; collector.MIN_FREE_BYTES + len(message) + 32768:&lt;br /&gt;
        raise FeedbackError(&amp;quot;insufficient free disk space to preserve the reviewed snapshot&amp;quot;)&lt;br /&gt;
    archive = root / &amp;quot;processed&amp;quot; / event_id&lt;br /&gt;
    archive.mkdir(mode=0o700)&lt;br /&gt;
    receipt = {&amp;quot;schema&amp;quot;: 1, &amp;quot;id&amp;quot;: event_id, &amp;quot;sha256&amp;quot;: metadata[&amp;quot;sha256&amp;quot;],&lt;br /&gt;
               &amp;quot;reviewed_utc&amp;quot;: collector.utc_now(), &amp;quot;status&amp;quot;: &amp;quot;preparing&amp;quot;,&lt;br /&gt;
               &amp;quot;selected_class&amp;quot;: label, &amp;quot;reason&amp;quot;: reason}&lt;br /&gt;
    write_private(archive / &amp;quot;admin.json&amp;quot;, receipt, json_value=True)&lt;br /&gt;
    write_private(archive / &amp;quot;message.eml&amp;quot;, message)&lt;br /&gt;
    write_private(archive / &amp;quot;metadata.json&amp;quot;, json.dumps(metadata, ensure_ascii=True).encode())&lt;br /&gt;
    # Keep the complete original event. Never traverse or execute files in source/.&lt;br /&gt;
    os.rename(root / &amp;quot;pending&amp;quot; / event_id, archive / &amp;quot;source&amp;quot;)&lt;br /&gt;
    receipt[&amp;quot;status&amp;quot;] = status&lt;br /&gt;
    update_receipt(archive, receipt)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;pending&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;processed&amp;quot;)&lt;br /&gt;
    return archive, receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def run_learning(message, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    executable = Path(executable)&lt;br /&gt;
    if not executable.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be an absolute path&amp;quot;)&lt;br /&gt;
    command = [str(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be an absolute regular file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be root-owned, mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        # rspamc reads the file itself; the secret never appears in argv or logs.&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;learn_&amp;quot; + label)&lt;br /&gt;
    # No -u/-d: one shared Bayes classifier. No shell and no automatic retry.&lt;br /&gt;
    return subprocess.run(command, input=message, stdout=subprocess.DEVNULL,&lt;br /&gt;
                          stderr=subprocess.DEVNULL, timeout=timeout, check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def learn_event(root, event_id, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid training class&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        prior = find_prior(root, metadata[&amp;quot;sha256&amp;quot;])&lt;br /&gt;
        if prior:&lt;br /&gt;
            raise FeedbackError(&amp;quot;this content has a prior learned or uncertain event; inspect it and reject the duplicate&amp;quot;)&lt;br /&gt;
        archive, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;learning&amp;quot;, label)&lt;br /&gt;
        try:&lt;br /&gt;
            rc = run_learning(message, label, executable=executable, controller=controller,&lt;br /&gt;
                              password_file=password_file, timeout=timeout)&lt;br /&gt;
        except subprocess.TimeoutExpired:&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client timeout; server may have learned; do not automatically retry&amp;quot;&lt;br /&gt;
        except (OSError, FeedbackError):&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_failed&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client could not start or local configuration was invalid&amp;quot;&lt;br /&gt;
        else:&lt;br /&gt;
            receipt[&amp;quot;returncode&amp;quot;] = rc&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if rc == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            if rc:&lt;br /&gt;
                receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; inspect server before any retry&amp;quot;&lt;br /&gt;
        receipt[&amp;quot;finished_utc&amp;quot;] = collector.utc_now()&lt;br /&gt;
        update_receipt(archive, receipt)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def reject_event(root, event_id, reason=&amp;quot;review rejected&amp;quot;):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if len(reason) &amp;gt; 500:&lt;br /&gt;
        raise FeedbackError(&amp;quot;reason is too long&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        _, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;rejected&amp;quot;, reason=reason)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def list_events(root, state, limit):&lt;br /&gt;
    count = 0&lt;br /&gt;
    for name in ((&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;) if state == &amp;quot;all&amp;quot; else (state,)):&lt;br /&gt;
        directory = root / name&lt;br /&gt;
        if not directory.exists():&lt;br /&gt;
            continue&lt;br /&gt;
        with Directory(directory):&lt;br /&gt;
            for entry in directory.iterdir():&lt;br /&gt;
                if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                if count &amp;gt;= limit:&lt;br /&gt;
                    emit({&amp;quot;notice&amp;quot;: &amp;quot;limit reached&amp;quot;, &amp;quot;limit&amp;quot;: limit})&lt;br /&gt;
                    return&lt;br /&gt;
                try:&lt;br /&gt;
                    with Directory(entry) as item:&lt;br /&gt;
                        metadata = json.loads(item.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    row = {key: metadata.get(key) for key in (&amp;quot;id&amp;quot;, &amp;quot;created_utc&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;user&amp;quot;, &amp;quot;source&amp;quot;, &amp;quot;destination&amp;quot;, &amp;quot;bytes&amp;quot;)}&lt;br /&gt;
                    row[&amp;quot;state&amp;quot;] = name&lt;br /&gt;
                    if name == &amp;quot;processed&amp;quot;:&lt;br /&gt;
                        row[&amp;quot;review&amp;quot;] = receipt_for(entry)&lt;br /&gt;
                    emit(row)&lt;br /&gt;
                except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                    emit({&amp;quot;id&amp;quot;: entry.name, &amp;quot;state&amp;quot;: name, &amp;quot;error&amp;quot;: &amp;quot;invalid event; manual inspection required&amp;quot;})&lt;br /&gt;
                count += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def show_event(root, event_id, state, path_only):&lt;br /&gt;
    metadata, message = read_event(root / state, check_id(event_id))&lt;br /&gt;
    if path_only:&lt;br /&gt;
        emit({&amp;quot;id&amp;quot;: event_id, &amp;quot;message_path&amp;quot;: str(root / state / event_id / &amp;quot;message.eml&amp;quot;)})&lt;br /&gt;
        return&lt;br /&gt;
    headers = BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message), 262144)])&lt;br /&gt;
    emit({&amp;quot;metadata&amp;quot;: metadata, &amp;quot;headers&amp;quot;: {name: [str(v)[:2000] for v in headers.get_all(name, [])[:10]] for name in HEADERS}})&lt;br /&gt;
    if state == &amp;quot;processed&amp;quot;:&lt;br /&gt;
        emit({&amp;quot;review&amp;quot;: receipt_for(root / state / event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def warn_processed_storage(root):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return&lt;br /&gt;
    total = count = 0&lt;br /&gt;
    for event in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(event.name) or event.is_symlink() or not event.is_dir():&lt;br /&gt;
            continue&lt;br /&gt;
        count += 1&lt;br /&gt;
        for prefix in (event, event / &amp;quot;source&amp;quot;):&lt;br /&gt;
            if prefix.is_symlink():&lt;br /&gt;
                continue&lt;br /&gt;
            for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    info = (prefix / name).lstat()&lt;br /&gt;
                    if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                        total += info.st_size&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    pass&lt;br /&gt;
    if total &amp;gt;= collector.MAX_PENDING_BYTES or count &amp;gt;= collector.MAX_PENDING_EVENTS:&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: WARNING processed archive reached 512 MiB or 1024 events; review retention manually; nothing deleted&amp;quot;, file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=DEFAULT_ROOT)&lt;br /&gt;
    sub = parser.add_subparsers(dest=&amp;quot;command&amp;quot;, required=True)&lt;br /&gt;
    listing = sub.add_parser(&amp;quot;list&amp;quot;, help=&amp;quot;JSON lines, metadata only&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;, &amp;quot;all&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--limit&amp;quot;, type=int, default=100)&lt;br /&gt;
    showing = sub.add_parser(&amp;quot;show&amp;quot;, help=&amp;quot;selected headers only; never dumps the body&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--path&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning = sub.add_parser(&amp;quot;learn&amp;quot;, help=&amp;quot;explicitly train one reviewed event, then archive it&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;label&amp;quot;, choices=(&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;))&lt;br /&gt;
    learning.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--password-file&amp;quot;, default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
                          help=&amp;quot;root-only plaintext secret file; use --no-password only for an intentionally trusted local controller&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--no-password&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    rejecting = sub.add_parser(&amp;quot;reject&amp;quot;, help=&amp;quot;archive one event without training or deleting it&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;--reason&amp;quot;, default=&amp;quot;review rejected&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    try:&lt;br /&gt;
        require_root()&lt;br /&gt;
        root = Path(args.queue_root)&lt;br /&gt;
        if not root.is_absolute():&lt;br /&gt;
            raise FeedbackError(&amp;quot;queue root must be absolute&amp;quot;)&lt;br /&gt;
        check_root_directory(root)&lt;br /&gt;
        warn_processed_storage(root)&lt;br /&gt;
        if args.command == &amp;quot;list&amp;quot;:&lt;br /&gt;
            if not 1 &amp;lt;= args.limit &amp;lt;= 10000:&lt;br /&gt;
                raise FeedbackError(&amp;quot;limit must be between 1 and 10000&amp;quot;)&lt;br /&gt;
            list_events(root, args.state, args.limit)&lt;br /&gt;
        elif args.command == &amp;quot;show&amp;quot;:&lt;br /&gt;
            show_event(root, args.id, args.state, args.path)&lt;br /&gt;
        elif args.command == &amp;quot;reject&amp;quot;:&lt;br /&gt;
            emit(reject_event(root, args.id, args.reason))&lt;br /&gt;
        else:&lt;br /&gt;
            if not 0 &amp;lt; args.timeout &amp;lt;= 300:&lt;br /&gt;
                raise FeedbackError(&amp;quot;timeout must be between 0 and 300 seconds&amp;quot;)&lt;br /&gt;
            result = learn_event(root, args.id, args.label, executable=args.rspamc_bin,&lt;br /&gt;
                                 controller=args.controller, password_file=None if args.no_password else args.password_file,&lt;br /&gt;
                                 timeout=args.timeout)&lt;br /&gt;
            emit(result)&lt;br /&gt;
            return 0 if result[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot; else 2&lt;br /&gt;
        return 0&lt;br /&gt;
    except (FeedbackError, OSError, ValueError, TypeError):&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: operation refused or failed; inspect configuration/event locally&amp;quot;, file=sys.stderr)&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    raise SystemExit(main())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only background learning from the bounded IMAPSieve feedback queue.&lt;br /&gt;
&lt;br /&gt;
The collector remains a local spool writer. This process owns controller&lt;br /&gt;
credentials, learns only validated INBOX/Junk events, preserves every original,&lt;br /&gt;
and never retries a training operation with an uncertain result. Run foreground&lt;br /&gt;
under FreeBSD daemon(8), or use --once for one bounded batch.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import signal&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
import threading&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
_spec = importlib.util.spec_from_file_location(&amp;quot;feedback_worker_admin&amp;quot;, Path(__file__).with_name(&amp;quot;feedback-admin.py&amp;quot;))&lt;br /&gt;
admin = importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError = admin.FeedbackError&lt;br /&gt;
SHA_RE = re.compile(r&amp;quot;[0-9a-f]{64}\Z&amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS = 20000&lt;br /&gt;
UNCERTAIN = {&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learn_uncertain&amp;quot;, &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;quarantined_ordering&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def log_event(kind, **fields):&lt;br /&gt;
    # Callers pass only fixed diagnostics, numeric counts, event IDs and classes.&lt;br /&gt;
    print(json.dumps({&amp;quot;event&amp;quot;: kind, **fields}, ensure_ascii=True, sort_keys=True), flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _timestamp_ns(value):&lt;br /&gt;
    if not isinstance(value, str):&lt;br /&gt;
        raise ValueError(&amp;quot;missing timestamp&amp;quot;)&lt;br /&gt;
    parsed = datetime.fromisoformat(value.replace(&amp;quot;Z&amp;quot;, &amp;quot;+00:00&amp;quot;))&lt;br /&gt;
    if parsed.tzinfo is None:&lt;br /&gt;
        raise ValueError(&amp;quot;timestamp must have timezone&amp;quot;)&lt;br /&gt;
    return int(parsed.timestamp() * 1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def order_events(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read bounded metadata, ordered by collector created_ns, then event ID.&lt;br /&gt;
&lt;br /&gt;
    Legacy second-resolution timestamps are permitted only when the same&lt;br /&gt;
    content has no contradictory labels at an identical timestamp. Such a tie&lt;br /&gt;
    cannot be resolved from random event IDs and is quarantined without learning.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    pending = Path(root) / &amp;quot;pending&amp;quot;&lt;br /&gt;
    rows = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    with admin.Directory(pending):&lt;br /&gt;
        with os.scandir(pending) as entries:&lt;br /&gt;
            for entry in entries:&lt;br /&gt;
                if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                count += 1&lt;br /&gt;
                if count &amp;gt; admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
                    raise FeedbackError(&amp;quot;pending queue scan bound exceeded&amp;quot;)&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(pending / entry.name) as directory:&lt;br /&gt;
                        metadata = json.loads(directory.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    if not isinstance(metadata, dict):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid metadata&amp;quot;)&lt;br /&gt;
                    metadata = dict(metadata)&lt;br /&gt;
                    if metadata.get(&amp;quot;id&amp;quot;) != entry.name or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid event identity&amp;quot;)&lt;br /&gt;
                    if not isinstance(metadata.get(&amp;quot;sha256&amp;quot;), str) or not SHA_RE.fullmatch(metadata[&amp;quot;sha256&amp;quot;]):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid digest&amp;quot;)&lt;br /&gt;
                    admin.collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                                                   metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
                    timestamp = metadata.get(&amp;quot;created_ns&amp;quot;)&lt;br /&gt;
                    if isinstance(timestamp, int) and not isinstance(timestamp, bool) and timestamp &amp;gt; 0:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = timestamp&lt;br /&gt;
                    elif &amp;quot;created_ns&amp;quot; not in metadata:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = _timestamp_ns(metadata.get(&amp;quot;created_utc&amp;quot;))&lt;br /&gt;
                    else:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = 0&lt;br /&gt;
                        metadata[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
                    rows.append(metadata)&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    # A manual administrator may have archived this candidate.&lt;br /&gt;
                    continue&lt;br /&gt;
                except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                    rows.append({&amp;quot;id&amp;quot;: entry.name, &amp;quot;_invalid&amp;quot;: True, &amp;quot;_order_ns&amp;quot;: 0})&lt;br /&gt;
    tied = {}&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
            tied.setdefault((row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;]), set()).add(row[&amp;quot;label&amp;quot;])&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;) and len(tied[(row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;])]) &amp;gt; 1:&lt;br /&gt;
            row[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
    return sorted(rows, key=lambda row: (row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;id&amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _journal(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read root-owned receipts; successful classes use durable worker sequence.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    processed = Path(root) / &amp;quot;processed&amp;quot;&lt;br /&gt;
    latest, blocked = {}, set()&lt;br /&gt;
    max_sequence = used_bytes = count = 0&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
    admin.check_root_directory(processed)&lt;br /&gt;
    with os.scandir(processed) as entries:&lt;br /&gt;
        for entry in entries:&lt;br /&gt;
            if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                continue&lt;br /&gt;
            count += 1&lt;br /&gt;
            if count &amp;gt; MAX_JOURNAL_EVENTS:&lt;br /&gt;
                raise FeedbackError(&amp;quot;processed journal scan bound exceeded&amp;quot;)&lt;br /&gt;
            directory = processed / entry.name&lt;br /&gt;
            receipt = admin.receipt_for(directory)&lt;br /&gt;
            if not isinstance(receipt, dict) or receipt.get(&amp;quot;id&amp;quot;) != entry.name:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed receipt&amp;quot;)&lt;br /&gt;
            digest = receipt.get(&amp;quot;sha256&amp;quot;)&lt;br /&gt;
            if not isinstance(digest, str) or not SHA_RE.fullmatch(digest):&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed digest&amp;quot;)&lt;br /&gt;
            sequence = receipt.get(&amp;quot;worker_sequence&amp;quot;, 0)&lt;br /&gt;
            if not isinstance(sequence, int) or isinstance(sequence, bool) or sequence &amp;lt; 0:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed sequence&amp;quot;)&lt;br /&gt;
            max_sequence = max(max_sequence, sequence)&lt;br /&gt;
            status = receipt.get(&amp;quot;status&amp;quot;)&lt;br /&gt;
            if status in UNCERTAIN:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
            if status == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                label = receipt.get(&amp;quot;selected_class&amp;quot;)&lt;br /&gt;
                if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
                    raise FeedbackError(&amp;quot;invalid learned class&amp;quot;)&lt;br /&gt;
                # Existing manual receipts predate automatic processing; the old&lt;br /&gt;
                # admin refuses further training of a SHA once it has a receipt.&lt;br /&gt;
                fallback = _timestamp_ns(receipt.get(&amp;quot;finished_utc&amp;quot;, receipt.get(&amp;quot;reviewed_utc&amp;quot;)))&lt;br /&gt;
                position = (bool(sequence), sequence if sequence else fallback, entry.name)&lt;br /&gt;
                if digest not in latest or position &amp;gt; latest[digest][0]:&lt;br /&gt;
                    latest[digest] = (position, label)&lt;br /&gt;
            # Count snapshots and preserved originals, never recursively walk&lt;br /&gt;
            # arbitrary files in the mail-user-owned source/ directory.&lt;br /&gt;
            for part in (directory, directory / &amp;quot;source&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(part) as safe:&lt;br /&gt;
                        for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                            try:&lt;br /&gt;
                                if safe.fd is not None:&lt;br /&gt;
                                    info = os.stat(name, dir_fd=safe.fd, follow_symlinks=False)&lt;br /&gt;
                                else:&lt;br /&gt;
                                    info = (part / name).lstat()&lt;br /&gt;
                                if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                                    used_bytes += info.st_size&lt;br /&gt;
                            except FileNotFoundError:&lt;br /&gt;
                                continue&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    continue&lt;br /&gt;
    return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _check_trusted_parent_chain(path):&lt;br /&gt;
    if os.name != &amp;quot;posix&amp;quot;:&lt;br /&gt;
        return&lt;br /&gt;
    for parent in Path(path).parents:&lt;br /&gt;
        link_info = parent.lstat()&lt;br /&gt;
        info = parent.stat()&lt;br /&gt;
        if (link_info.st_uid != 0 or not stat.S_ISDIR(info.st_mode)&lt;br /&gt;
                or info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;executable parent directories must be controlled by root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def resolve_executable(executable):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Allow FreeBSD package symlinks, but pin both operations to their target.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    source = Path(executable)&lt;br /&gt;
    if not source.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be absolute&amp;quot;)&lt;br /&gt;
    source_info = source.lstat()&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and source_info.st_uid != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc entry must be owned by root&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(source)&lt;br /&gt;
    target = source.resolve(strict=True)&lt;br /&gt;
    info = target.lstat()&lt;br /&gt;
    if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be a regular file&amp;quot;)&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be root-owned and not writable by mail users&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(target)&lt;br /&gt;
    return str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def controller_ready(executable, controller, password_file, timeout):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Authenticated read-only controller probe; no candidate is archived yet.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    command = [resolve_executable(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;unsafe controller password file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;controller password file must be root-owned mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;stat&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        return subprocess.run(command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,&lt;br /&gt;
                              stderr=subprocess.DEVNULL, check=False, timeout=min(timeout, 5)).returncode == 0&lt;br /&gt;
    except (subprocess.TimeoutExpired, OSError):&lt;br /&gt;
        return False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def process_pending(root, *, executable=&amp;quot;/usr/local/bin/rspamc&amp;quot;, controller=&amp;quot;127.0.0.1:11334&amp;quot;,&lt;br /&gt;
                    password_file=admin.DEFAULT_PASSWORD_FILE, timeout=30, batch_size=20,&lt;br /&gt;
                    preflight=None, learner=None, stop_event=None):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Process at most batch_size candidates. Inject probes/learner for unit tests.&lt;br /&gt;
&lt;br /&gt;
    One admin lock serializes manual and background operations. A crash after a&lt;br /&gt;
    durable learning intent leaves status=learning, which blocks this SHA on the&lt;br /&gt;
    next pass; subsequent candidates are preserved as quarantined_uncertain.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    root = Path(root)&lt;br /&gt;
    preflight = controller_ready if preflight is None else preflight&lt;br /&gt;
    if learner is None:&lt;br /&gt;
        # FreeBSD pkg installs rspamc as a root-owned versioned symlink. Resolve&lt;br /&gt;
        # it once so preflight and training invoke the same trusted executable.&lt;br /&gt;
        executable = resolve_executable(executable)&lt;br /&gt;
        learner = admin.run_learning&lt;br /&gt;
    summary = {&amp;quot;learned&amp;quot;: 0, &amp;quot;duplicates&amp;quot;: 0, &amp;quot;quarantined&amp;quot;: 0, &amp;quot;invalid&amp;quot;: 0,&lt;br /&gt;
               &amp;quot;pending&amp;quot;: 0, &amp;quot;controller_unavailable&amp;quot;: False, &amp;quot;storage_blocked&amp;quot;: False,&lt;br /&gt;
               &amp;quot;archive_warning&amp;quot;: False}&lt;br /&gt;
    with admin.admin_lock(root):&lt;br /&gt;
        events = order_events(root)&lt;br /&gt;
        summary[&amp;quot;pending&amp;quot;] = len(events)&lt;br /&gt;
        if not events:&lt;br /&gt;
            return summary&lt;br /&gt;
        latest, blocked, sequence, used_bytes, archive_count = _journal(root)&lt;br /&gt;
        summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
        if used_bytes &amp;gt;= admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
            summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        if not preflight(executable, controller, password_file, timeout):&lt;br /&gt;
            summary[&amp;quot;controller_unavailable&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        handled = 0&lt;br /&gt;
        for candidate in events:&lt;br /&gt;
            if handled &amp;gt;= batch_size or (stop_event is not None and stop_event.is_set()):&lt;br /&gt;
                break&lt;br /&gt;
            if candidate.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            event_id = candidate[&amp;quot;id&amp;quot;]&lt;br /&gt;
            if (root / &amp;quot;processed&amp;quot; / event_id).exists():&lt;br /&gt;
                # Crash before prepare_archive moved pending/ID: its root-owned&lt;br /&gt;
                # intent is authoritative. Preserve the pending original for&lt;br /&gt;
                # manual recovery and continue independent content, never retry.&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            try:&lt;br /&gt;
                metadata, message = admin.read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
            except FileNotFoundError:&lt;br /&gt;
                continue&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            # Do not act if the queued ordering/identity changed since enumeration.&lt;br /&gt;
            if any(metadata.get(key) != candidate.get(key) for key in&lt;br /&gt;
                   (&amp;quot;sha256&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;created_ns&amp;quot;, &amp;quot;created_utc&amp;quot;)):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            if used_bytes + 2 * len(message) + 32768 &amp;gt; admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            label, digest = metadata[&amp;quot;label&amp;quot;], metadata[&amp;quot;sha256&amp;quot;]&lt;br /&gt;
            if candidate.get(&amp;quot;_ordering_invalid&amp;quot;):&lt;br /&gt;
                status, reason = &amp;quot;quarantined_ordering&amp;quot;, &amp;quot;event ordering is ambiguous&amp;quot;&lt;br /&gt;
            elif digest in blocked:&lt;br /&gt;
                status, reason = &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;a prior learning operation has an uncertain result&amp;quot;&lt;br /&gt;
            elif digest in latest and latest[digest][1] == label:&lt;br /&gt;
                status, reason = &amp;quot;duplicate&amp;quot;, &amp;quot;same class as the latest successful learning operation&amp;quot;&lt;br /&gt;
            else:&lt;br /&gt;
                status, reason = &amp;quot;learning&amp;quot;, &amp;quot;automatic explicit IMAP feedback&amp;quot;&lt;br /&gt;
            try:&lt;br /&gt;
                archive, receipt = admin.prepare_archive(root, event_id, metadata, message, status, label, reason)&lt;br /&gt;
            except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                # prepare_archive writes a root journal before moving the source;&lt;br /&gt;
                # stop instead of retrying an incompletely prepared operation.&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            sequence += 1&lt;br /&gt;
            receipt.update({&amp;quot;mode&amp;quot;: &amp;quot;automatic_imap_feedback&amp;quot;, &amp;quot;worker_sequence&amp;quot;: sequence,&lt;br /&gt;
                            &amp;quot;processed_ns&amp;quot;: time.time_ns(), &amp;quot;source_created_ns&amp;quot;: candidate[&amp;quot;_order_ns&amp;quot;]})&lt;br /&gt;
            if digest in latest:&lt;br /&gt;
                receipt[&amp;quot;previous_successful_class&amp;quot;] = latest[digest][1]&lt;br /&gt;
            # Durable intent exists before the only call that may change Bayes.&lt;br /&gt;
            admin.update_receipt(archive, receipt)&lt;br /&gt;
            if status == &amp;quot;learning&amp;quot;:&lt;br /&gt;
                try:&lt;br /&gt;
                    returncode = learner(message, label, executable=executable, controller=controller,&lt;br /&gt;
                                         password_file=password_file, timeout=timeout)&lt;br /&gt;
                except (subprocess.TimeoutExpired, OSError, FeedbackError):&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    receipt[&amp;quot;error&amp;quot;] = &amp;quot;client failure; server may have learned; no automatic retry&amp;quot;&lt;br /&gt;
                else:&lt;br /&gt;
                    receipt[&amp;quot;returncode&amp;quot;] = returncode&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if returncode == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    if returncode != 0:&lt;br /&gt;
                        receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; no automatic retry&amp;quot;&lt;br /&gt;
                receipt[&amp;quot;finished_utc&amp;quot;] = admin.collector.utc_now()&lt;br /&gt;
                admin.update_receipt(archive, receipt)&lt;br /&gt;
                if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                    latest[digest] = ((True, sequence, event_id), label)&lt;br /&gt;
                    summary[&amp;quot;learned&amp;quot;] += 1&lt;br /&gt;
                else:&lt;br /&gt;
                    blocked.add(digest)&lt;br /&gt;
                    summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            elif status == &amp;quot;duplicate&amp;quot;:&lt;br /&gt;
                summary[&amp;quot;duplicates&amp;quot;] += 1&lt;br /&gt;
            else:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
                summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            handled += 1&lt;br /&gt;
            used_bytes += 2 * len(message) + 32768&lt;br /&gt;
            archive_count += 1&lt;br /&gt;
            summary[&amp;quot;pending&amp;quot;] -= 1&lt;br /&gt;
            summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
            log_event(&amp;quot;feedback_processed&amp;quot;, id=event_id, label=label, status=receipt[&amp;quot;status&amp;quot;])&lt;br /&gt;
            if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learn_uncertain&amp;quot;:&lt;br /&gt;
                # The controller may have failed after the preflight. Leave all&lt;br /&gt;
                # other candidates pending until a fresh read-only probe passes.&lt;br /&gt;
                break&lt;br /&gt;
    return summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=admin.DEFAULT_ROOT)&lt;br /&gt;
    parser.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--password-file&amp;quot;, default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
    parser.add_argument(&amp;quot;--poll&amp;quot;, type=float, default=2)&lt;br /&gt;
    parser.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    parser.add_argument(&amp;quot;--batch-size&amp;quot;, type=int, default=20)&lt;br /&gt;
    parser.add_argument(&amp;quot;--once&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    admin.require_root()&lt;br /&gt;
    root = Path(args.queue_root)&lt;br /&gt;
    if not root.is_absolute() or not 0.2 &amp;lt;= args.poll &amp;lt;= 60 or not 0 &amp;lt; args.timeout &amp;lt;= 300 or not 1 &amp;lt;= args.batch_size &amp;lt;= 100:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid worker options&amp;quot;)&lt;br /&gt;
    admin.check_root_directory(root)&lt;br /&gt;
    stopping = threading.Event()&lt;br /&gt;
    for signum in (signal.SIGTERM, signal.SIGINT):&lt;br /&gt;
        signal.signal(signum, lambda *_: stopping.set())&lt;br /&gt;
    lockfd = os.open(root / &amp;quot;.worker.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)&lt;br /&gt;
        previous_state = None&lt;br /&gt;
        while not stopping.is_set():&lt;br /&gt;
            try:&lt;br /&gt;
                summary = process_pending(root, executable=args.rspamc_bin, controller=args.controller,&lt;br /&gt;
                                          password_file=args.password_file, timeout=args.timeout,&lt;br /&gt;
                                          batch_size=args.batch_size, stop_event=stopping)&lt;br /&gt;
                state = {key: value for key, value in summary.items() if key not in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)}&lt;br /&gt;
                if state != previous_state or any(summary[key] for key in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)):&lt;br /&gt;
                    log_event(&amp;quot;worker_state&amp;quot;, **summary)&lt;br /&gt;
                previous_state = state&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 2 if summary[&amp;quot;controller_unavailable&amp;quot;] or summary[&amp;quot;storage_blocked&amp;quot;] else 0&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                if previous_state != &amp;quot;local_error&amp;quot;:&lt;br /&gt;
                    log_event(&amp;quot;worker_error&amp;quot;, reason=&amp;quot;local queue, journal or configuration failure; no automatic retry of uncertain training&amp;quot;)&lt;br /&gt;
                previous_state = &amp;quot;local_error&amp;quot;&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 1&lt;br /&gt;
            stopping.wait(args.poll)&lt;br /&gt;
        return 0&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    try:&lt;br /&gt;
        raise SystemExit(main())&lt;br /&gt;
    except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
        log_event(&amp;quot;worker_start_failed&amp;quot;, reason=&amp;quot;root, configuration or worker lock required&amp;quot;)&lt;br /&gt;
        raise SystemExit(1)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=686</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=686"/>
		<updated>2026-10-29T23:51:08Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* /usr/local/libexec/rspamd-feedback/feedback-admin.py */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет → TCP 25 / STARTTLS → Exim&lt;br /&gt;
Клиент   → TCP 465 TLS или 587 STARTTLS + AUTH → Exim&lt;br /&gt;
Exim     → LMTP Unix socket → Dovecot → /opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim     → AUTH Unix socket → Dovecot → файловый каталог пользователей&lt;br /&gt;
Клиент   → TCP 993 TLS → Dovecot&lt;br /&gt;
SnappyMail (необязательно) → те же IMAPS и SMTP AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
sysrc hostname=mail.example.org&lt;br /&gt;
hostname mail.example.org&lt;br /&gt;
sysrc defaultrouter=198.51.100.1&lt;br /&gt;
# Только если в работающей таблице отсутствует default route:&lt;br /&gt;
route add default 198.51.100.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo /dev/da1 | awk &#039;{print $3}&#039;)&lt;br /&gt;
test -n &amp;quot;$disk_bytes&amp;quot; &amp;amp;&amp;amp; test &amp;quot;$disk_bytes&amp;quot; -gt 0 || exit 1&lt;br /&gt;
cmp -s -n &amp;quot;$disk_bytes&amp;quot; /dev/da1 /dev/zero&lt;br /&gt;
printf &#039;cmp status: %s\n&#039; &amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
pkg update&lt;br /&gt;
pkg install -n exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
# После проверки плана:&lt;br /&gt;
pkg install -y exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
sysrc exim_enable=NO dovecot_enable=NO&lt;br /&gt;
sysrc sendmail_enable=NONE sendmail_submit_enable=NO&lt;br /&gt;
sysrc sendmail_outbound_enable=NO sendmail_msp_queue_enable=NO&lt;br /&gt;
service sendmail onestop&lt;br /&gt;
/usr/local/sbin/exim -bV&lt;br /&gt;
/usr/local/sbin/dovecot --version&lt;br /&gt;
pw usershow mailnull&lt;br /&gt;
pw groupshow mail&lt;br /&gt;
pw groupshow dovecot&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only review and explicit training of IMAPSieve feedback.&lt;br /&gt;
&lt;br /&gt;
No command trains automatically. list/show are local reads. learn ID spam|ham&lt;br /&gt;
is the only command that invokes rspamc. reject ID archives without training.&lt;br /&gt;
Queue root and processed must be root-owned, non-writable by mail users;&lt;br /&gt;
pending is separately owned by vmail, mode 0700. Archived originals remain in&lt;br /&gt;
processed/ID/source; only root-owned snapshots are used for training or review.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from contextlib import contextmanager&lt;br /&gt;
from email import policy&lt;br /&gt;
from email.parser import BytesHeaderParser&lt;br /&gt;
import hashlib&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import shutil&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
&lt;br /&gt;
spec = importlib.util.spec_from_file_location(&amp;quot;feedback_collector&amp;quot;, Path(__file__).with_name(&amp;quot;feedback.py&amp;quot;))&lt;br /&gt;
collector = importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE = collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError = collector.FeedbackError&lt;br /&gt;
ID_RE = re.compile(r&amp;quot;[0-9a-f]{32}\Z&amp;quot;)&lt;br /&gt;
DEFAULT_ROOT = &amp;quot;/opt/mail/rspamd-feedback&amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE = &amp;quot;/usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
HEADERS = (&amp;quot;From&amp;quot;, &amp;quot;To&amp;quot;, &amp;quot;Date&amp;quot;, &amp;quot;Subject&amp;quot;, &amp;quot;Message-ID&amp;quot;, &amp;quot;X-Spam-Status&amp;quot;, &amp;quot;X-Spam-Score&amp;quot;, &amp;quot;X-Rspamd-Action&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def emit(value):&lt;br /&gt;
    print(json.dumps(value, ensure_ascii=True, sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def require_root():&lt;br /&gt;
    if not hasattr(os, &amp;quot;geteuid&amp;quot;) or os.geteuid() != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;this administrative command requires root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_id(event_id):&lt;br /&gt;
    if not ID_RE.fullmatch(event_id):&lt;br /&gt;
        raise FeedbackError(&amp;quot;ID must be exactly 32 lowercase hexadecimal characters&amp;quot;)&lt;br /&gt;
    return event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class Directory:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Nofollow directory descriptor on FreeBSD; portable fallback for local tests.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def __init__(self, path):&lt;br /&gt;
        self.path = Path(path)&lt;br /&gt;
        info = self.path.lstat()&lt;br /&gt;
        if not stat.S_ISDIR(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;expected a real directory&amp;quot;)&lt;br /&gt;
        self.fd = None&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            self.fd = os.open(self.path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
    def __enter__(self):&lt;br /&gt;
        return self&lt;br /&gt;
&lt;br /&gt;
    def __exit__(self, *args):&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
    def read(self, name, limit):&lt;br /&gt;
        if &amp;quot;/&amp;quot; in name or &amp;quot;\\&amp;quot; in name or name in (&amp;quot;.&amp;quot;, &amp;quot;..&amp;quot;):&lt;br /&gt;
            raise FeedbackError(&amp;quot;invalid internal filename&amp;quot;)&lt;br /&gt;
        if self.fd is not None:&lt;br /&gt;
            fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=self.fd)&lt;br /&gt;
        else:&lt;br /&gt;
            path = self.path / name&lt;br /&gt;
            if path.is_symlink():&lt;br /&gt;
                raise FeedbackError(&amp;quot;symlink is not an event file&amp;quot;)&lt;br /&gt;
            fd = os.open(path, os.O_RDONLY | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0))&lt;br /&gt;
        with os.fdopen(fd, &amp;quot;rb&amp;quot;) as source:&lt;br /&gt;
            info = os.fstat(source.fileno())&lt;br /&gt;
            if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_size &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file is not a bounded, single-link regular file&amp;quot;)&lt;br /&gt;
            result = source.read(limit + 1)&lt;br /&gt;
            if len(result) &amp;gt; limit:&lt;br /&gt;
                raise FeedbackError(&amp;quot;event file exceeded the size limit&amp;quot;)&lt;br /&gt;
            return result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def read_event(directory, event_id):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    # On POSIX open the event relative to the already opened parent descriptor.&lt;br /&gt;
    # The parent path is deployment-owned, and a symlink event is rejected.&lt;br /&gt;
    with Directory(Path(directory) / event_id) as entry:&lt;br /&gt;
        metadata = json.loads(entry.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
        message = entry.read(&amp;quot;message.eml&amp;quot;, MAX_MESSAGE)&lt;br /&gt;
    if not isinstance(metadata, dict) or not isinstance(metadata.get(&amp;quot;user&amp;quot;), str):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                             metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
    if metadata.get(&amp;quot;id&amp;quot;) != event_id or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid event metadata&amp;quot;)&lt;br /&gt;
    if metadata.get(&amp;quot;bytes&amp;quot;) != len(message) or metadata.get(&amp;quot;sha256&amp;quot;) != hashlib.sha256(message).hexdigest():&lt;br /&gt;
        raise FeedbackError(&amp;quot;event content does not match its metadata&amp;quot;)&lt;br /&gt;
    return metadata, message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def write_private(path, value, *, json_value=False):&lt;br /&gt;
    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, &amp;quot;O_BINARY&amp;quot;, 0)&lt;br /&gt;
    fd = os.open(path, flags, 0o600)&lt;br /&gt;
    with os.fdopen(fd, &amp;quot;wb&amp;quot;) as output:&lt;br /&gt;
        if json_value:&lt;br /&gt;
            value = (json.dumps(value, ensure_ascii=True, sort_keys=True) + &amp;quot;\n&amp;quot;).encode()&lt;br /&gt;
        output.write(value)&lt;br /&gt;
        output.flush()&lt;br /&gt;
        os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def update_receipt(directory, receipt):&lt;br /&gt;
    temporary = directory / &amp;quot;.admin-new.json&amp;quot;&lt;br /&gt;
    write_private(temporary, receipt, json_value=True)&lt;br /&gt;
    os.replace(temporary, directory / &amp;quot;admin.json&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def check_root_directory(path):&lt;br /&gt;
    with Directory(path):&lt;br /&gt;
        info = Path(path).stat()&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;administrative directories must be root-owned and not group/other writable&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def admin_lock(root):&lt;br /&gt;
    check_root_directory(root)&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        processed.mkdir(mode=0o700)&lt;br /&gt;
    check_root_directory(processed)&lt;br /&gt;
    fd = os.open(root / &amp;quot;.admin.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(fd, fcntl.LOCK_EX)&lt;br /&gt;
        yield&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def receipt_for(directory):&lt;br /&gt;
    with Directory(directory) as entry:&lt;br /&gt;
        return json.loads(entry.read(&amp;quot;admin.json&amp;quot;, 16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def find_prior(root, sha256):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return None&lt;br /&gt;
    for entry in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
            continue&lt;br /&gt;
        try:&lt;br /&gt;
            receipt = receipt_for(entry)&lt;br /&gt;
        except (OSError, ValueError, FeedbackError):&lt;br /&gt;
            # A damaged journal must never silently allow repeated training.&lt;br /&gt;
            raise FeedbackError(&amp;quot;processed journal is incomplete; inspect it before more training&amp;quot;)&lt;br /&gt;
        if receipt.get(&amp;quot;sha256&amp;quot;) == sha256 and receipt.get(&amp;quot;status&amp;quot;) in (&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learned&amp;quot;, &amp;quot;learn_uncertain&amp;quot;):&lt;br /&gt;
            return receipt&lt;br /&gt;
    return None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def prepare_archive(root, event_id, metadata, message, status, label=None, reason=None):&lt;br /&gt;
    if shutil.disk_usage(root).free &amp;lt; collector.MIN_FREE_BYTES + len(message) + 32768:&lt;br /&gt;
        raise FeedbackError(&amp;quot;insufficient free disk space to preserve the reviewed snapshot&amp;quot;)&lt;br /&gt;
    archive = root / &amp;quot;processed&amp;quot; / event_id&lt;br /&gt;
    archive.mkdir(mode=0o700)&lt;br /&gt;
    receipt = {&amp;quot;schema&amp;quot;: 1, &amp;quot;id&amp;quot;: event_id, &amp;quot;sha256&amp;quot;: metadata[&amp;quot;sha256&amp;quot;],&lt;br /&gt;
               &amp;quot;reviewed_utc&amp;quot;: collector.utc_now(), &amp;quot;status&amp;quot;: &amp;quot;preparing&amp;quot;,&lt;br /&gt;
               &amp;quot;selected_class&amp;quot;: label, &amp;quot;reason&amp;quot;: reason}&lt;br /&gt;
    write_private(archive / &amp;quot;admin.json&amp;quot;, receipt, json_value=True)&lt;br /&gt;
    write_private(archive / &amp;quot;message.eml&amp;quot;, message)&lt;br /&gt;
    write_private(archive / &amp;quot;metadata.json&amp;quot;, json.dumps(metadata, ensure_ascii=True).encode())&lt;br /&gt;
    # Keep the complete original event. Never traverse or execute files in source/.&lt;br /&gt;
    os.rename(root / &amp;quot;pending&amp;quot; / event_id, archive / &amp;quot;source&amp;quot;)&lt;br /&gt;
    receipt[&amp;quot;status&amp;quot;] = status&lt;br /&gt;
    update_receipt(archive, receipt)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;pending&amp;quot;)&lt;br /&gt;
    collector.fsync_directory(root / &amp;quot;processed&amp;quot;)&lt;br /&gt;
    return archive, receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def run_learning(message, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    executable = Path(executable)&lt;br /&gt;
    if not executable.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be an absolute path&amp;quot;)&lt;br /&gt;
    command = [str(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be an absolute regular file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;password file must be root-owned, mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        # rspamc reads the file itself; the secret never appears in argv or logs.&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;learn_&amp;quot; + label)&lt;br /&gt;
    # No -u/-d: one shared Bayes classifier. No shell and no automatic retry.&lt;br /&gt;
    return subprocess.run(command, input=message, stdout=subprocess.DEVNULL,&lt;br /&gt;
                          stderr=subprocess.DEVNULL, timeout=timeout, check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def learn_event(root, event_id, label, *, executable, controller, password_file=None, timeout=30):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid training class&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        prior = find_prior(root, metadata[&amp;quot;sha256&amp;quot;])&lt;br /&gt;
        if prior:&lt;br /&gt;
            raise FeedbackError(&amp;quot;this content has a prior learned or uncertain event; inspect it and reject the duplicate&amp;quot;)&lt;br /&gt;
        archive, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;learning&amp;quot;, label)&lt;br /&gt;
        try:&lt;br /&gt;
            rc = run_learning(message, label, executable=executable, controller=controller,&lt;br /&gt;
                              password_file=password_file, timeout=timeout)&lt;br /&gt;
        except subprocess.TimeoutExpired:&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client timeout; server may have learned; do not automatically retry&amp;quot;&lt;br /&gt;
        except (OSError, FeedbackError):&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_failed&amp;quot;&lt;br /&gt;
            receipt[&amp;quot;error&amp;quot;] = &amp;quot;client could not start or local configuration was invalid&amp;quot;&lt;br /&gt;
        else:&lt;br /&gt;
            receipt[&amp;quot;returncode&amp;quot;] = rc&lt;br /&gt;
            receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if rc == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
            if rc:&lt;br /&gt;
                receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; inspect server before any retry&amp;quot;&lt;br /&gt;
        receipt[&amp;quot;finished_utc&amp;quot;] = collector.utc_now()&lt;br /&gt;
        update_receipt(archive, receipt)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def reject_event(root, event_id, reason=&amp;quot;review rejected&amp;quot;):&lt;br /&gt;
    check_id(event_id)&lt;br /&gt;
    if len(reason) &amp;gt; 500:&lt;br /&gt;
        raise FeedbackError(&amp;quot;reason is too long&amp;quot;)&lt;br /&gt;
    with admin_lock(root):&lt;br /&gt;
        metadata, message = read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
        _, receipt = prepare_archive(root, event_id, metadata, message, &amp;quot;rejected&amp;quot;, reason=reason)&lt;br /&gt;
        return receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def list_events(root, state, limit):&lt;br /&gt;
    count = 0&lt;br /&gt;
    for name in ((&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;) if state == &amp;quot;all&amp;quot; else (state,)):&lt;br /&gt;
        directory = root / name&lt;br /&gt;
        if not directory.exists():&lt;br /&gt;
            continue&lt;br /&gt;
        with Directory(directory):&lt;br /&gt;
            for entry in directory.iterdir():&lt;br /&gt;
                if not ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                if count &amp;gt;= limit:&lt;br /&gt;
                    emit({&amp;quot;notice&amp;quot;: &amp;quot;limit reached&amp;quot;, &amp;quot;limit&amp;quot;: limit})&lt;br /&gt;
                    return&lt;br /&gt;
                try:&lt;br /&gt;
                    with Directory(entry) as item:&lt;br /&gt;
                        metadata = json.loads(item.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    row = {key: metadata.get(key) for key in (&amp;quot;id&amp;quot;, &amp;quot;created_utc&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;user&amp;quot;, &amp;quot;source&amp;quot;, &amp;quot;destination&amp;quot;, &amp;quot;bytes&amp;quot;)}&lt;br /&gt;
                    row[&amp;quot;state&amp;quot;] = name&lt;br /&gt;
                    if name == &amp;quot;processed&amp;quot;:&lt;br /&gt;
                        row[&amp;quot;review&amp;quot;] = receipt_for(entry)&lt;br /&gt;
                    emit(row)&lt;br /&gt;
                except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                    emit({&amp;quot;id&amp;quot;: entry.name, &amp;quot;state&amp;quot;: name, &amp;quot;error&amp;quot;: &amp;quot;invalid event; manual inspection required&amp;quot;})&lt;br /&gt;
                count += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def show_event(root, event_id, state, path_only):&lt;br /&gt;
    metadata, message = read_event(root / state, check_id(event_id))&lt;br /&gt;
    if path_only:&lt;br /&gt;
        emit({&amp;quot;id&amp;quot;: event_id, &amp;quot;message_path&amp;quot;: str(root / state / event_id / &amp;quot;message.eml&amp;quot;)})&lt;br /&gt;
        return&lt;br /&gt;
    headers = BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message), 262144)])&lt;br /&gt;
    emit({&amp;quot;metadata&amp;quot;: metadata, &amp;quot;headers&amp;quot;: {name: [str(v)[:2000] for v in headers.get_all(name, [])[:10]] for name in HEADERS}})&lt;br /&gt;
    if state == &amp;quot;processed&amp;quot;:&lt;br /&gt;
        emit({&amp;quot;review&amp;quot;: receipt_for(root / state / event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def warn_processed_storage(root):&lt;br /&gt;
    processed = root / &amp;quot;processed&amp;quot;&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return&lt;br /&gt;
    total = count = 0&lt;br /&gt;
    for event in processed.iterdir():&lt;br /&gt;
        if not ID_RE.fullmatch(event.name) or event.is_symlink() or not event.is_dir():&lt;br /&gt;
            continue&lt;br /&gt;
        count += 1&lt;br /&gt;
        for prefix in (event, event / &amp;quot;source&amp;quot;):&lt;br /&gt;
            if prefix.is_symlink():&lt;br /&gt;
                continue&lt;br /&gt;
            for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    info = (prefix / name).lstat()&lt;br /&gt;
                    if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                        total += info.st_size&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    pass&lt;br /&gt;
    if total &amp;gt;= collector.MAX_PENDING_BYTES or count &amp;gt;= collector.MAX_PENDING_EVENTS:&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: WARNING processed archive reached 512 MiB or 1024 events; review retention manually; nothing deleted&amp;quot;, file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=DEFAULT_ROOT)&lt;br /&gt;
    sub = parser.add_subparsers(dest=&amp;quot;command&amp;quot;, required=True)&lt;br /&gt;
    listing = sub.add_parser(&amp;quot;list&amp;quot;, help=&amp;quot;JSON lines, metadata only&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;, &amp;quot;all&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    listing.add_argument(&amp;quot;--limit&amp;quot;, type=int, default=100)&lt;br /&gt;
    showing = sub.add_parser(&amp;quot;show&amp;quot;, help=&amp;quot;selected headers only; never dumps the body&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--state&amp;quot;, choices=(&amp;quot;pending&amp;quot;, &amp;quot;processed&amp;quot;), default=&amp;quot;pending&amp;quot;)&lt;br /&gt;
    showing.add_argument(&amp;quot;--path&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning = sub.add_parser(&amp;quot;learn&amp;quot;, help=&amp;quot;explicitly train one reviewed event, then archive it&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;label&amp;quot;, choices=(&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;))&lt;br /&gt;
    learning.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--password-file&amp;quot;, default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
                          help=&amp;quot;root-only plaintext secret file; use --no-password only for an intentionally trusted local controller&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--no-password&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    learning.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    rejecting = sub.add_parser(&amp;quot;reject&amp;quot;, help=&amp;quot;archive one event without training or deleting it&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;id&amp;quot;)&lt;br /&gt;
    rejecting.add_argument(&amp;quot;--reason&amp;quot;, default=&amp;quot;review rejected&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    try:&lt;br /&gt;
        require_root()&lt;br /&gt;
        root = Path(args.queue_root)&lt;br /&gt;
        if not root.is_absolute():&lt;br /&gt;
            raise FeedbackError(&amp;quot;queue root must be absolute&amp;quot;)&lt;br /&gt;
        check_root_directory(root)&lt;br /&gt;
        warn_processed_storage(root)&lt;br /&gt;
        if args.command == &amp;quot;list&amp;quot;:&lt;br /&gt;
            if not 1 &amp;lt;= args.limit &amp;lt;= 10000:&lt;br /&gt;
                raise FeedbackError(&amp;quot;limit must be between 1 and 10000&amp;quot;)&lt;br /&gt;
            list_events(root, args.state, args.limit)&lt;br /&gt;
        elif args.command == &amp;quot;show&amp;quot;:&lt;br /&gt;
            show_event(root, args.id, args.state, args.path)&lt;br /&gt;
        elif args.command == &amp;quot;reject&amp;quot;:&lt;br /&gt;
            emit(reject_event(root, args.id, args.reason))&lt;br /&gt;
        else:&lt;br /&gt;
            if not 0 &amp;lt; args.timeout &amp;lt;= 300:&lt;br /&gt;
                raise FeedbackError(&amp;quot;timeout must be between 0 and 300 seconds&amp;quot;)&lt;br /&gt;
            result = learn_event(root, args.id, args.label, executable=args.rspamc_bin,&lt;br /&gt;
                                 controller=args.controller, password_file=None if args.no_password else args.password_file,&lt;br /&gt;
                                 timeout=args.timeout)&lt;br /&gt;
            emit(result)&lt;br /&gt;
            return 0 if result[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot; else 2&lt;br /&gt;
        return 0&lt;br /&gt;
    except (FeedbackError, OSError, ValueError, TypeError):&lt;br /&gt;
        print(&amp;quot;rspamd-feedback-admin: operation refused or failed; inspect configuration/event locally&amp;quot;, file=sys.stderr)&lt;br /&gt;
        return 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    raise SystemExit(main())&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only background learning from the bounded IMAPSieve feedback queue.&lt;br /&gt;
&lt;br /&gt;
The collector remains a local spool writer. This process owns controller&lt;br /&gt;
credentials, learns only validated INBOX/Junk events, preserves every original,&lt;br /&gt;
and never retries a training operation with an uncertain result. Run foreground&lt;br /&gt;
under FreeBSD daemon(8), or use --once for one bounded batch.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import signal&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
import threading&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
_spec = importlib.util.spec_from_file_location(&amp;quot;feedback_worker_admin&amp;quot;, Path(__file__).with_name(&amp;quot;feedback-admin.py&amp;quot;))&lt;br /&gt;
admin = importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError = admin.FeedbackError&lt;br /&gt;
SHA_RE = re.compile(r&amp;quot;[0-9a-f]{64}\Z&amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS = 20000&lt;br /&gt;
UNCERTAIN = {&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learn_uncertain&amp;quot;, &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;quarantined_ordering&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def log_event(kind, **fields):&lt;br /&gt;
    # Callers pass only fixed diagnostics, numeric counts, event IDs and classes.&lt;br /&gt;
    print(json.dumps({&amp;quot;event&amp;quot;: kind, **fields}, ensure_ascii=True, sort_keys=True), flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _timestamp_ns(value):&lt;br /&gt;
    if not isinstance(value, str):&lt;br /&gt;
        raise ValueError(&amp;quot;missing timestamp&amp;quot;)&lt;br /&gt;
    parsed = datetime.fromisoformat(value.replace(&amp;quot;Z&amp;quot;, &amp;quot;+00:00&amp;quot;))&lt;br /&gt;
    if parsed.tzinfo is None:&lt;br /&gt;
        raise ValueError(&amp;quot;timestamp must have timezone&amp;quot;)&lt;br /&gt;
    return int(parsed.timestamp() * 1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def order_events(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read bounded metadata, ordered by collector created_ns, then event ID.&lt;br /&gt;
&lt;br /&gt;
    Legacy second-resolution timestamps are permitted only when the same&lt;br /&gt;
    content has no contradictory labels at an identical timestamp. Such a tie&lt;br /&gt;
    cannot be resolved from random event IDs and is quarantined without learning.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    pending = Path(root) / &amp;quot;pending&amp;quot;&lt;br /&gt;
    rows = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    with admin.Directory(pending):&lt;br /&gt;
        with os.scandir(pending) as entries:&lt;br /&gt;
            for entry in entries:&lt;br /&gt;
                if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                count += 1&lt;br /&gt;
                if count &amp;gt; admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
                    raise FeedbackError(&amp;quot;pending queue scan bound exceeded&amp;quot;)&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(pending / entry.name) as directory:&lt;br /&gt;
                        metadata = json.loads(directory.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    if not isinstance(metadata, dict):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid metadata&amp;quot;)&lt;br /&gt;
                    metadata = dict(metadata)&lt;br /&gt;
                    if metadata.get(&amp;quot;id&amp;quot;) != entry.name or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid event identity&amp;quot;)&lt;br /&gt;
                    if not isinstance(metadata.get(&amp;quot;sha256&amp;quot;), str) or not SHA_RE.fullmatch(metadata[&amp;quot;sha256&amp;quot;]):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid digest&amp;quot;)&lt;br /&gt;
                    admin.collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                                                   metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
                    timestamp = metadata.get(&amp;quot;created_ns&amp;quot;)&lt;br /&gt;
                    if isinstance(timestamp, int) and not isinstance(timestamp, bool) and timestamp &amp;gt; 0:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = timestamp&lt;br /&gt;
                    elif &amp;quot;created_ns&amp;quot; not in metadata:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = _timestamp_ns(metadata.get(&amp;quot;created_utc&amp;quot;))&lt;br /&gt;
                    else:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = 0&lt;br /&gt;
                        metadata[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
                    rows.append(metadata)&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    # A manual administrator may have archived this candidate.&lt;br /&gt;
                    continue&lt;br /&gt;
                except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                    rows.append({&amp;quot;id&amp;quot;: entry.name, &amp;quot;_invalid&amp;quot;: True, &amp;quot;_order_ns&amp;quot;: 0})&lt;br /&gt;
    tied = {}&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
            tied.setdefault((row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;]), set()).add(row[&amp;quot;label&amp;quot;])&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;) and len(tied[(row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;])]) &amp;gt; 1:&lt;br /&gt;
            row[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
    return sorted(rows, key=lambda row: (row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;id&amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _journal(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read root-owned receipts; successful classes use durable worker sequence.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    processed = Path(root) / &amp;quot;processed&amp;quot;&lt;br /&gt;
    latest, blocked = {}, set()&lt;br /&gt;
    max_sequence = used_bytes = count = 0&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
    admin.check_root_directory(processed)&lt;br /&gt;
    with os.scandir(processed) as entries:&lt;br /&gt;
        for entry in entries:&lt;br /&gt;
            if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                continue&lt;br /&gt;
            count += 1&lt;br /&gt;
            if count &amp;gt; MAX_JOURNAL_EVENTS:&lt;br /&gt;
                raise FeedbackError(&amp;quot;processed journal scan bound exceeded&amp;quot;)&lt;br /&gt;
            directory = processed / entry.name&lt;br /&gt;
            receipt = admin.receipt_for(directory)&lt;br /&gt;
            if not isinstance(receipt, dict) or receipt.get(&amp;quot;id&amp;quot;) != entry.name:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed receipt&amp;quot;)&lt;br /&gt;
            digest = receipt.get(&amp;quot;sha256&amp;quot;)&lt;br /&gt;
            if not isinstance(digest, str) or not SHA_RE.fullmatch(digest):&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed digest&amp;quot;)&lt;br /&gt;
            sequence = receipt.get(&amp;quot;worker_sequence&amp;quot;, 0)&lt;br /&gt;
            if not isinstance(sequence, int) or isinstance(sequence, bool) or sequence &amp;lt; 0:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed sequence&amp;quot;)&lt;br /&gt;
            max_sequence = max(max_sequence, sequence)&lt;br /&gt;
            status = receipt.get(&amp;quot;status&amp;quot;)&lt;br /&gt;
            if status in UNCERTAIN:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
            if status == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                label = receipt.get(&amp;quot;selected_class&amp;quot;)&lt;br /&gt;
                if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
                    raise FeedbackError(&amp;quot;invalid learned class&amp;quot;)&lt;br /&gt;
                # Existing manual receipts predate automatic processing; the old&lt;br /&gt;
                # admin refuses further training of a SHA once it has a receipt.&lt;br /&gt;
                fallback = _timestamp_ns(receipt.get(&amp;quot;finished_utc&amp;quot;, receipt.get(&amp;quot;reviewed_utc&amp;quot;)))&lt;br /&gt;
                position = (bool(sequence), sequence if sequence else fallback, entry.name)&lt;br /&gt;
                if digest not in latest or position &amp;gt; latest[digest][0]:&lt;br /&gt;
                    latest[digest] = (position, label)&lt;br /&gt;
            # Count snapshots and preserved originals, never recursively walk&lt;br /&gt;
            # arbitrary files in the mail-user-owned source/ directory.&lt;br /&gt;
            for part in (directory, directory / &amp;quot;source&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(part) as safe:&lt;br /&gt;
                        for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                            try:&lt;br /&gt;
                                if safe.fd is not None:&lt;br /&gt;
                                    info = os.stat(name, dir_fd=safe.fd, follow_symlinks=False)&lt;br /&gt;
                                else:&lt;br /&gt;
                                    info = (part / name).lstat()&lt;br /&gt;
                                if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                                    used_bytes += info.st_size&lt;br /&gt;
                            except FileNotFoundError:&lt;br /&gt;
                                continue&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    continue&lt;br /&gt;
    return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _check_trusted_parent_chain(path):&lt;br /&gt;
    if os.name != &amp;quot;posix&amp;quot;:&lt;br /&gt;
        return&lt;br /&gt;
    for parent in Path(path).parents:&lt;br /&gt;
        link_info = parent.lstat()&lt;br /&gt;
        info = parent.stat()&lt;br /&gt;
        if (link_info.st_uid != 0 or not stat.S_ISDIR(info.st_mode)&lt;br /&gt;
                or info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;executable parent directories must be controlled by root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def resolve_executable(executable):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Allow FreeBSD package symlinks, but pin both operations to their target.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    source = Path(executable)&lt;br /&gt;
    if not source.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be absolute&amp;quot;)&lt;br /&gt;
    source_info = source.lstat()&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and source_info.st_uid != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc entry must be owned by root&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(source)&lt;br /&gt;
    target = source.resolve(strict=True)&lt;br /&gt;
    info = target.lstat()&lt;br /&gt;
    if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be a regular file&amp;quot;)&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be root-owned and not writable by mail users&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(target)&lt;br /&gt;
    return str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def controller_ready(executable, controller, password_file, timeout):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Authenticated read-only controller probe; no candidate is archived yet.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    command = [resolve_executable(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;unsafe controller password file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;controller password file must be root-owned mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;stat&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        return subprocess.run(command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,&lt;br /&gt;
                              stderr=subprocess.DEVNULL, check=False, timeout=min(timeout, 5)).returncode == 0&lt;br /&gt;
    except (subprocess.TimeoutExpired, OSError):&lt;br /&gt;
        return False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def process_pending(root, *, executable=&amp;quot;/usr/local/bin/rspamc&amp;quot;, controller=&amp;quot;127.0.0.1:11334&amp;quot;,&lt;br /&gt;
                    password_file=admin.DEFAULT_PASSWORD_FILE, timeout=30, batch_size=20,&lt;br /&gt;
                    preflight=None, learner=None, stop_event=None):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Process at most batch_size candidates. Inject probes/learner for unit tests.&lt;br /&gt;
&lt;br /&gt;
    One admin lock serializes manual and background operations. A crash after a&lt;br /&gt;
    durable learning intent leaves status=learning, which blocks this SHA on the&lt;br /&gt;
    next pass; subsequent candidates are preserved as quarantined_uncertain.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    root = Path(root)&lt;br /&gt;
    preflight = controller_ready if preflight is None else preflight&lt;br /&gt;
    if learner is None:&lt;br /&gt;
        # FreeBSD pkg installs rspamc as a root-owned versioned symlink. Resolve&lt;br /&gt;
        # it once so preflight and training invoke the same trusted executable.&lt;br /&gt;
        executable = resolve_executable(executable)&lt;br /&gt;
        learner = admin.run_learning&lt;br /&gt;
    summary = {&amp;quot;learned&amp;quot;: 0, &amp;quot;duplicates&amp;quot;: 0, &amp;quot;quarantined&amp;quot;: 0, &amp;quot;invalid&amp;quot;: 0,&lt;br /&gt;
               &amp;quot;pending&amp;quot;: 0, &amp;quot;controller_unavailable&amp;quot;: False, &amp;quot;storage_blocked&amp;quot;: False,&lt;br /&gt;
               &amp;quot;archive_warning&amp;quot;: False}&lt;br /&gt;
    with admin.admin_lock(root):&lt;br /&gt;
        events = order_events(root)&lt;br /&gt;
        summary[&amp;quot;pending&amp;quot;] = len(events)&lt;br /&gt;
        if not events:&lt;br /&gt;
            return summary&lt;br /&gt;
        latest, blocked, sequence, used_bytes, archive_count = _journal(root)&lt;br /&gt;
        summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
        if used_bytes &amp;gt;= admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
            summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        if not preflight(executable, controller, password_file, timeout):&lt;br /&gt;
            summary[&amp;quot;controller_unavailable&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        handled = 0&lt;br /&gt;
        for candidate in events:&lt;br /&gt;
            if handled &amp;gt;= batch_size or (stop_event is not None and stop_event.is_set()):&lt;br /&gt;
                break&lt;br /&gt;
            if candidate.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            event_id = candidate[&amp;quot;id&amp;quot;]&lt;br /&gt;
            if (root / &amp;quot;processed&amp;quot; / event_id).exists():&lt;br /&gt;
                # Crash before prepare_archive moved pending/ID: its root-owned&lt;br /&gt;
                # intent is authoritative. Preserve the pending original for&lt;br /&gt;
                # manual recovery and continue independent content, never retry.&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            try:&lt;br /&gt;
                metadata, message = admin.read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
            except FileNotFoundError:&lt;br /&gt;
                continue&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            # Do not act if the queued ordering/identity changed since enumeration.&lt;br /&gt;
            if any(metadata.get(key) != candidate.get(key) for key in&lt;br /&gt;
                   (&amp;quot;sha256&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;created_ns&amp;quot;, &amp;quot;created_utc&amp;quot;)):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            if used_bytes + 2 * len(message) + 32768 &amp;gt; admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            label, digest = metadata[&amp;quot;label&amp;quot;], metadata[&amp;quot;sha256&amp;quot;]&lt;br /&gt;
            if candidate.get(&amp;quot;_ordering_invalid&amp;quot;):&lt;br /&gt;
                status, reason = &amp;quot;quarantined_ordering&amp;quot;, &amp;quot;event ordering is ambiguous&amp;quot;&lt;br /&gt;
            elif digest in blocked:&lt;br /&gt;
                status, reason = &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;a prior learning operation has an uncertain result&amp;quot;&lt;br /&gt;
            elif digest in latest and latest[digest][1] == label:&lt;br /&gt;
                status, reason = &amp;quot;duplicate&amp;quot;, &amp;quot;same class as the latest successful learning operation&amp;quot;&lt;br /&gt;
            else:&lt;br /&gt;
                status, reason = &amp;quot;learning&amp;quot;, &amp;quot;automatic explicit IMAP feedback&amp;quot;&lt;br /&gt;
            try:&lt;br /&gt;
                archive, receipt = admin.prepare_archive(root, event_id, metadata, message, status, label, reason)&lt;br /&gt;
            except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                # prepare_archive writes a root journal before moving the source;&lt;br /&gt;
                # stop instead of retrying an incompletely prepared operation.&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            sequence += 1&lt;br /&gt;
            receipt.update({&amp;quot;mode&amp;quot;: &amp;quot;automatic_imap_feedback&amp;quot;, &amp;quot;worker_sequence&amp;quot;: sequence,&lt;br /&gt;
                            &amp;quot;processed_ns&amp;quot;: time.time_ns(), &amp;quot;source_created_ns&amp;quot;: candidate[&amp;quot;_order_ns&amp;quot;]})&lt;br /&gt;
            if digest in latest:&lt;br /&gt;
                receipt[&amp;quot;previous_successful_class&amp;quot;] = latest[digest][1]&lt;br /&gt;
            # Durable intent exists before the only call that may change Bayes.&lt;br /&gt;
            admin.update_receipt(archive, receipt)&lt;br /&gt;
            if status == &amp;quot;learning&amp;quot;:&lt;br /&gt;
                try:&lt;br /&gt;
                    returncode = learner(message, label, executable=executable, controller=controller,&lt;br /&gt;
                                         password_file=password_file, timeout=timeout)&lt;br /&gt;
                except (subprocess.TimeoutExpired, OSError, FeedbackError):&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    receipt[&amp;quot;error&amp;quot;] = &amp;quot;client failure; server may have learned; no automatic retry&amp;quot;&lt;br /&gt;
                else:&lt;br /&gt;
                    receipt[&amp;quot;returncode&amp;quot;] = returncode&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if returncode == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    if returncode != 0:&lt;br /&gt;
                        receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; no automatic retry&amp;quot;&lt;br /&gt;
                receipt[&amp;quot;finished_utc&amp;quot;] = admin.collector.utc_now()&lt;br /&gt;
                admin.update_receipt(archive, receipt)&lt;br /&gt;
                if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                    latest[digest] = ((True, sequence, event_id), label)&lt;br /&gt;
                    summary[&amp;quot;learned&amp;quot;] += 1&lt;br /&gt;
                else:&lt;br /&gt;
                    blocked.add(digest)&lt;br /&gt;
                    summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            elif status == &amp;quot;duplicate&amp;quot;:&lt;br /&gt;
                summary[&amp;quot;duplicates&amp;quot;] += 1&lt;br /&gt;
            else:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
                summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            handled += 1&lt;br /&gt;
            used_bytes += 2 * len(message) + 32768&lt;br /&gt;
            archive_count += 1&lt;br /&gt;
            summary[&amp;quot;pending&amp;quot;] -= 1&lt;br /&gt;
            summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
            log_event(&amp;quot;feedback_processed&amp;quot;, id=event_id, label=label, status=receipt[&amp;quot;status&amp;quot;])&lt;br /&gt;
            if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learn_uncertain&amp;quot;:&lt;br /&gt;
                # The controller may have failed after the preflight. Leave all&lt;br /&gt;
                # other candidates pending until a fresh read-only probe passes.&lt;br /&gt;
                break&lt;br /&gt;
    return summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=admin.DEFAULT_ROOT)&lt;br /&gt;
    parser.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--password-file&amp;quot;, default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
    parser.add_argument(&amp;quot;--poll&amp;quot;, type=float, default=2)&lt;br /&gt;
    parser.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    parser.add_argument(&amp;quot;--batch-size&amp;quot;, type=int, default=20)&lt;br /&gt;
    parser.add_argument(&amp;quot;--once&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    admin.require_root()&lt;br /&gt;
    root = Path(args.queue_root)&lt;br /&gt;
    if not root.is_absolute() or not 0.2 &amp;lt;= args.poll &amp;lt;= 60 or not 0 &amp;lt; args.timeout &amp;lt;= 300 or not 1 &amp;lt;= args.batch_size &amp;lt;= 100:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid worker options&amp;quot;)&lt;br /&gt;
    admin.check_root_directory(root)&lt;br /&gt;
    stopping = threading.Event()&lt;br /&gt;
    for signum in (signal.SIGTERM, signal.SIGINT):&lt;br /&gt;
        signal.signal(signum, lambda *_: stopping.set())&lt;br /&gt;
    lockfd = os.open(root / &amp;quot;.worker.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)&lt;br /&gt;
        previous_state = None&lt;br /&gt;
        while not stopping.is_set():&lt;br /&gt;
            try:&lt;br /&gt;
                summary = process_pending(root, executable=args.rspamc_bin, controller=args.controller,&lt;br /&gt;
                                          password_file=args.password_file, timeout=args.timeout,&lt;br /&gt;
                                          batch_size=args.batch_size, stop_event=stopping)&lt;br /&gt;
                state = {key: value for key, value in summary.items() if key not in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)}&lt;br /&gt;
                if state != previous_state or any(summary[key] for key in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)):&lt;br /&gt;
                    log_event(&amp;quot;worker_state&amp;quot;, **summary)&lt;br /&gt;
                previous_state = state&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 2 if summary[&amp;quot;controller_unavailable&amp;quot;] or summary[&amp;quot;storage_blocked&amp;quot;] else 0&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                if previous_state != &amp;quot;local_error&amp;quot;:&lt;br /&gt;
                    log_event(&amp;quot;worker_error&amp;quot;, reason=&amp;quot;local queue, journal or configuration failure; no automatic retry of uncertain training&amp;quot;)&lt;br /&gt;
                previous_state = &amp;quot;local_error&amp;quot;&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 1&lt;br /&gt;
            stopping.wait(args.poll)&lt;br /&gt;
        return 0&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    try:&lt;br /&gt;
        raise SystemExit(main())&lt;br /&gt;
    except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
        log_event(&amp;quot;worker_start_failed&amp;quot;, reason=&amp;quot;root, configuration or worker lock required&amp;quot;)&lt;br /&gt;
        raise SystemExit(1)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=685</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=685"/>
		<updated>2026-10-29T23:49:17Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* /usr/local/libexec/rspamd-feedback/feedback-worker.py */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет → TCP 25 / STARTTLS → Exim&lt;br /&gt;
Клиент   → TCP 465 TLS или 587 STARTTLS + AUTH → Exim&lt;br /&gt;
Exim     → LMTP Unix socket → Dovecot → /opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim     → AUTH Unix socket → Dovecot → файловый каталог пользователей&lt;br /&gt;
Клиент   → TCP 993 TLS → Dovecot&lt;br /&gt;
SnappyMail (необязательно) → те же IMAPS и SMTP AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
sysrc hostname=mail.example.org&lt;br /&gt;
hostname mail.example.org&lt;br /&gt;
sysrc defaultrouter=198.51.100.1&lt;br /&gt;
# Только если в работающей таблице отсутствует default route:&lt;br /&gt;
route add default 198.51.100.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo /dev/da1 | awk &#039;{print $3}&#039;)&lt;br /&gt;
test -n &amp;quot;$disk_bytes&amp;quot; &amp;amp;&amp;amp; test &amp;quot;$disk_bytes&amp;quot; -gt 0 || exit 1&lt;br /&gt;
cmp -s -n &amp;quot;$disk_bytes&amp;quot; /dev/da1 /dev/zero&lt;br /&gt;
printf &#039;cmp status: %s\n&#039; &amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
pkg update&lt;br /&gt;
pkg install -n exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
# После проверки плана:&lt;br /&gt;
pkg install -y exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
sysrc exim_enable=NO dovecot_enable=NO&lt;br /&gt;
sysrc sendmail_enable=NONE sendmail_submit_enable=NO&lt;br /&gt;
sysrc sendmail_outbound_enable=NO sendmail_msp_queue_enable=NO&lt;br /&gt;
service sendmail onestop&lt;br /&gt;
/usr/local/sbin/exim -bV&lt;br /&gt;
/usr/local/sbin/dovecot --version&lt;br /&gt;
pw usershow mailnull&lt;br /&gt;
pw groupshow mail&lt;br /&gt;
pw groupshow dovecot&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;review&amp;amp;#32;and&amp;amp;#32;explicit&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback.&lt;br /&gt;
&lt;br /&gt;
No&amp;amp;#32;command&amp;amp;#32;trains&amp;amp;#32;automatically.&amp;amp;#32;list/show&amp;amp;#32;are&amp;amp;#32;local&amp;amp;#32;reads.&amp;amp;#32;learn&amp;amp;#32;ID&amp;amp;#32;spam|ham&lt;br /&gt;
is&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;command&amp;amp;#32;that&amp;amp;#32;invokes&amp;amp;#32;rspamc.&amp;amp;#32;reject&amp;amp;#32;ID&amp;amp;#32;archives&amp;amp;#32;without&amp;amp;#32;training.&lt;br /&gt;
Queue&amp;amp;#32;root&amp;amp;#32;and&amp;amp;#32;processed&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;non-writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users;&lt;br /&gt;
pending&amp;amp;#32;is&amp;amp;#32;separately&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;vmail,&amp;amp;#32;mode&amp;amp;#32;0700.&amp;amp;#32;Archived&amp;amp;#32;originals&amp;amp;#32;remain&amp;amp;#32;in&lt;br /&gt;
processed/ID/source;&amp;amp;#32;only&amp;amp;#32;root-owned&amp;amp;#32;snapshots&amp;amp;#32;are&amp;amp;#32;used&amp;amp;#32;for&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;review.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;email&amp;amp;#32;import&amp;amp;#32;policy&lt;br /&gt;
from&amp;amp;#32;email.parser&amp;amp;#32;import&amp;amp;#32;BytesHeaderParser&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
&lt;br /&gt;
spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_collector&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback.py&amp;amp;quot;))&lt;br /&gt;
collector&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;collector.FeedbackError&lt;br /&gt;
ID_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{32}\Z&amp;amp;quot;)&lt;br /&gt;
DEFAULT_ROOT&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback&amp;amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/usr/local/etc/rspamd/controller.password&amp;amp;quot;&lt;br /&gt;
HEADERS&amp;amp;#32;=&amp;amp;#32;(&amp;amp;quot;From&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;To&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Date&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Subject&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Message-ID&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Status&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Score&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Rspamd-Action&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;emit(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;require_root():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;hasattr(os,&amp;amp;#32;&amp;amp;quot;geteuid&amp;amp;quot;)&amp;amp;#32;or&amp;amp;#32;os.geteuid()&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;administrative&amp;amp;#32;command&amp;amp;#32;requires&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_id(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;ID&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;exactly&amp;amp;#32;32&amp;amp;#32;lowercase&amp;amp;#32;hexadecimal&amp;amp;#32;characters&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;Directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Nofollow&amp;amp;#32;directory&amp;amp;#32;descriptor&amp;amp;#32;on&amp;amp;#32;FreeBSD;&amp;amp;#32;portable&amp;amp;#32;fallback&amp;amp;#32;for&amp;amp;#32;local&amp;amp;#32;tests.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__init__(self,&amp;amp;#32;path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;self.path.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;expected&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;os.open(self.path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__enter__(self):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;self&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__exit__(self,&amp;amp;#32;*args):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;read(self,&amp;amp;#32;name,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;&amp;amp;quot;\\&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;.&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;..&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;internal&amp;amp;#32;filename&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(name,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW&amp;amp;#32;|&amp;amp;#32;os.O_NONBLOCK,&amp;amp;#32;dir_fd=self.fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;self.path&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;symlink&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;event&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;rb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;source:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.fstat(source.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;info.st_nlink&amp;amp;#32;!=&amp;amp;#32;1&amp;amp;#32;or&amp;amp;#32;info.st_size&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;bounded,&amp;amp;#32;single-link&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;source.read(limit&amp;amp;#32;+&amp;amp;#32;1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(result)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;exceeded&amp;amp;#32;the&amp;amp;#32;size&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;read_event(directory,&amp;amp;#32;event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;On&amp;amp;#32;POSIX&amp;amp;#32;open&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;relative&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;already&amp;amp;#32;opened&amp;amp;#32;parent&amp;amp;#32;descriptor.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;parent&amp;amp;#32;path&amp;amp;#32;is&amp;amp;#32;deployment-owned,&amp;amp;#32;and&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;event&amp;amp;#32;is&amp;amp;#32;rejected.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(Path(directory)&amp;amp;#32;/&amp;amp;#32;event_id)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;entry.read(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;MAX_MESSAGE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;user&amp;amp;quot;),&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;event_id&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;bytes&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;len(message)&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;hashlib.sha256(message).hexdigest():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;content&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;its&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata,&amp;amp;#32;message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write_private(path,&amp;amp;#32;value,&amp;amp;#32;*,&amp;amp;#32;json_value=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;flags,&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;json_value:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;value&amp;amp;#32;=&amp;amp;#32;(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;\n&amp;amp;quot;).encode()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(value)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;update_receipt(directory,&amp;amp;#32;receipt):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin-new.json&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(temporary,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.replace(temporary,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_root_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;Path(path).stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;administrative&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;group/other&amp;amp;#32;writable&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;receipt_for(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;find_prior(root,&amp;amp;#32;sha256):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;damaged&amp;amp;#32;journal&amp;amp;#32;must&amp;amp;#32;never&amp;amp;#32;silently&amp;amp;#32;allow&amp;amp;#32;repeated&amp;amp;#32;training.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;is&amp;amp;#32;incomplete;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;before&amp;amp;#32;more&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;==&amp;amp;#32;sha256&amp;amp;#32;and&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label=None,&amp;amp;#32;reason=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(root).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;collector.MIN_FREE_BYTES&amp;amp;#32;+&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;#32;to&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;reviewed&amp;amp;#32;snapshot&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;],&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;reviewed_utc&amp;amp;quot;:&amp;amp;#32;collector.utc_now(),&amp;amp;#32;&amp;amp;quot;status&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;preparing&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;selected_class&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;reason&amp;amp;quot;:&amp;amp;#32;reason}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;json.dumps(metadata,&amp;amp;#32;ensure_ascii=True).encode())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;complete&amp;amp;#32;original&amp;amp;#32;event.&amp;amp;#32;Never&amp;amp;#32;traverse&amp;amp;#32;or&amp;amp;#32;execute&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;source/.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id,&amp;amp;#32;archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;status&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;archive,&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;executable.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;path&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[str(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;rspamc&amp;amp;#32;reads&amp;amp;#32;the&amp;amp;#32;file&amp;amp;#32;itself;&amp;amp;#32;the&amp;amp;#32;secret&amp;amp;#32;never&amp;amp;#32;appears&amp;amp;#32;in&amp;amp;#32;argv&amp;amp;#32;or&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;learn_&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;-u/-d:&amp;amp;#32;one&amp;amp;#32;shared&amp;amp;#32;Bayes&amp;amp;#32;classifier.&amp;amp;#32;No&amp;amp;#32;shell&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;input=message,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;timeout=timeout,&amp;amp;#32;check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;learn_event(root,&amp;amp;#32;event_id,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;training&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;prior&amp;amp;#32;=&amp;amp;#32;find_prior(root,&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prior:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;prior&amp;amp;#32;learned&amp;amp;#32;or&amp;amp;#32;uncertain&amp;amp;#32;event;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;and&amp;amp;#32;reject&amp;amp;#32;the&amp;amp;#32;duplicate&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rc&amp;amp;#32;=&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;subprocess.TimeoutExpired:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;timeout;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;automatically&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_failed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;could&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;or&amp;amp;#32;local&amp;amp;#32;configuration&amp;amp;#32;was&amp;amp;#32;invalid&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;rc&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;rc&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;rc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;inspect&amp;amp;#32;server&amp;amp;#32;before&amp;amp;#32;any&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;reject_event(root,&amp;amp;#32;event_id,&amp;amp;#32;reason=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(reason)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;500:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;reason&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;rejected&amp;amp;quot;,&amp;amp;#32;reason=reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;list_events(root,&amp;amp;#32;state,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;((&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;(state,)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;directory.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;directory.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;notice&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;limit&amp;amp;#32;reached&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;limit&amp;amp;quot;:&amp;amp;#32;limit})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(entry)&amp;amp;#32;as&amp;amp;#32;item:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(item.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;metadata.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;id&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;state&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;review&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(row)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;state&amp;amp;quot;:&amp;amp;#32;name,&amp;amp;#32;&amp;amp;quot;error&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;#32;event;&amp;amp;#32;manual&amp;amp;#32;inspection&amp;amp;#32;required&amp;amp;quot;})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;show_event(root,&amp;amp;#32;event_id,&amp;amp;#32;state,&amp;amp;#32;path_only):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;state,&amp;amp;#32;check_id(event_id))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path_only:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;message_path&amp;amp;quot;:&amp;amp;#32;str(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;)})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;headers&amp;amp;#32;=&amp;amp;#32;BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message),&amp;amp;#32;262144)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;metadata&amp;amp;quot;:&amp;amp;#32;metadata,&amp;amp;#32;&amp;amp;quot;headers&amp;amp;quot;:&amp;amp;#32;{name:&amp;amp;#32;[str(v)[:2000]&amp;amp;#32;for&amp;amp;#32;v&amp;amp;#32;in&amp;amp;#32;headers.get_all(name,&amp;amp;#32;[])[:10]]&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;HEADERS}})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;review&amp;amp;quot;:&amp;amp;#32;receipt_for(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;warn_processed_storage(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;event&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event.name)&amp;amp;#32;or&amp;amp;#32;event.is_symlink()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;event.is_dir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;prefix&amp;amp;#32;in&amp;amp;#32;(event,&amp;amp;#32;event&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prefix.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(prefix&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_BYTES&amp;amp;#32;or&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;WARNING&amp;amp;#32;processed&amp;amp;#32;archive&amp;amp;#32;reached&amp;amp;#32;512&amp;amp;#32;MiB&amp;amp;#32;or&amp;amp;#32;1024&amp;amp;#32;events;&amp;amp;#32;review&amp;amp;#32;retention&amp;amp;#32;manually;&amp;amp;#32;nothing&amp;amp;#32;deleted&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sub&amp;amp;#32;=&amp;amp;#32;parser.add_subparsers(dest=&amp;amp;quot;command&amp;amp;quot;,&amp;amp;#32;required=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;list&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;JSON&amp;amp;#32;lines,&amp;amp;#32;metadata&amp;amp;#32;only&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--limit&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=100)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;show&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;selected&amp;amp;#32;headers&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;dumps&amp;amp;#32;the&amp;amp;#32;body&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--path&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;learn&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;explicitly&amp;amp;#32;train&amp;amp;#32;one&amp;amp;#32;reviewed&amp;amp;#32;event,&amp;amp;#32;then&amp;amp;#32;archive&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;help=&amp;amp;quot;root-only&amp;amp;#32;plaintext&amp;amp;#32;secret&amp;amp;#32;file;&amp;amp;#32;use&amp;amp;#32;--no-password&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;an&amp;amp;#32;intentionally&amp;amp;#32;trusted&amp;amp;#32;local&amp;amp;#32;controller&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--no-password&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;reject&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;archive&amp;amp;#32;one&amp;amp;#32;event&amp;amp;#32;without&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;deleting&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;--reason&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;root&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn_processed_storage(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;list&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.limit&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;10000:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;limit&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;1&amp;amp;#32;and&amp;amp;#32;10000&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;list_events(root,&amp;amp;#32;args.state,&amp;amp;#32;args.limit)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;show&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;show_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.state,&amp;amp;#32;args.path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;reject&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(reject_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.reason))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;timeout&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;0&amp;amp;#32;and&amp;amp;#32;300&amp;amp;#32;seconds&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;learn_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.label,&amp;amp;#32;executable=args.rspamc_bin,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;controller=args.controller,&amp;amp;#32;password_file=None&amp;amp;#32;if&amp;amp;#32;args.no_password&amp;amp;#32;else&amp;amp;#32;args.password_file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timeout=args.timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(result)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&amp;amp;#32;if&amp;amp;#32;result[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;operation&amp;amp;#32;refused&amp;amp;#32;or&amp;amp;#32;failed;&amp;amp;#32;inspect&amp;amp;#32;configuration/event&amp;amp;#32;locally&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;Root-only background learning from the bounded IMAPSieve feedback queue.&lt;br /&gt;
&lt;br /&gt;
The collector remains a local spool writer. This process owns controller&lt;br /&gt;
credentials, learns only validated INBOX/Junk events, preserves every original,&lt;br /&gt;
and never retries a training operation with an uncertain result. Run foreground&lt;br /&gt;
under FreeBSD daemon(8), or use --once for one bounded batch.&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
from __future__ import annotations&lt;br /&gt;
&lt;br /&gt;
import argparse&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
import importlib.util&lt;br /&gt;
import json&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import re&lt;br /&gt;
import signal&lt;br /&gt;
import stat&lt;br /&gt;
import subprocess&lt;br /&gt;
import sys&lt;br /&gt;
import threading&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
_spec = importlib.util.spec_from_file_location(&amp;quot;feedback_worker_admin&amp;quot;, Path(__file__).with_name(&amp;quot;feedback-admin.py&amp;quot;))&lt;br /&gt;
admin = importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError = admin.FeedbackError&lt;br /&gt;
SHA_RE = re.compile(r&amp;quot;[0-9a-f]{64}\Z&amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS = 20000&lt;br /&gt;
UNCERTAIN = {&amp;quot;preparing&amp;quot;, &amp;quot;learning&amp;quot;, &amp;quot;learn_uncertain&amp;quot;, &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;quarantined_ordering&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def log_event(kind, **fields):&lt;br /&gt;
    # Callers pass only fixed diagnostics, numeric counts, event IDs and classes.&lt;br /&gt;
    print(json.dumps({&amp;quot;event&amp;quot;: kind, **fields}, ensure_ascii=True, sort_keys=True), flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _timestamp_ns(value):&lt;br /&gt;
    if not isinstance(value, str):&lt;br /&gt;
        raise ValueError(&amp;quot;missing timestamp&amp;quot;)&lt;br /&gt;
    parsed = datetime.fromisoformat(value.replace(&amp;quot;Z&amp;quot;, &amp;quot;+00:00&amp;quot;))&lt;br /&gt;
    if parsed.tzinfo is None:&lt;br /&gt;
        raise ValueError(&amp;quot;timestamp must have timezone&amp;quot;)&lt;br /&gt;
    return int(parsed.timestamp() * 1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def order_events(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read bounded metadata, ordered by collector created_ns, then event ID.&lt;br /&gt;
&lt;br /&gt;
    Legacy second-resolution timestamps are permitted only when the same&lt;br /&gt;
    content has no contradictory labels at an identical timestamp. Such a tie&lt;br /&gt;
    cannot be resolved from random event IDs and is quarantined without learning.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    pending = Path(root) / &amp;quot;pending&amp;quot;&lt;br /&gt;
    rows = []&lt;br /&gt;
    count = 0&lt;br /&gt;
    with admin.Directory(pending):&lt;br /&gt;
        with os.scandir(pending) as entries:&lt;br /&gt;
            for entry in entries:&lt;br /&gt;
                if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                    continue&lt;br /&gt;
                count += 1&lt;br /&gt;
                if count &amp;gt; admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
                    raise FeedbackError(&amp;quot;pending queue scan bound exceeded&amp;quot;)&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(pending / entry.name) as directory:&lt;br /&gt;
                        metadata = json.loads(directory.read(&amp;quot;metadata.json&amp;quot;, 16384))&lt;br /&gt;
                    if not isinstance(metadata, dict):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid metadata&amp;quot;)&lt;br /&gt;
                    metadata = dict(metadata)&lt;br /&gt;
                    if metadata.get(&amp;quot;id&amp;quot;) != entry.name or metadata.get(&amp;quot;schema&amp;quot;) != 1:&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid event identity&amp;quot;)&lt;br /&gt;
                    if not isinstance(metadata.get(&amp;quot;sha256&amp;quot;), str) or not SHA_RE.fullmatch(metadata[&amp;quot;sha256&amp;quot;]):&lt;br /&gt;
                        raise ValueError(&amp;quot;invalid digest&amp;quot;)&lt;br /&gt;
                    admin.collector.validate_event(metadata.get(&amp;quot;label&amp;quot;), metadata.get(&amp;quot;user&amp;quot;, &amp;quot;&amp;quot;),&lt;br /&gt;
                                                   metadata.get(&amp;quot;source&amp;quot;), metadata.get(&amp;quot;destination&amp;quot;))&lt;br /&gt;
                    timestamp = metadata.get(&amp;quot;created_ns&amp;quot;)&lt;br /&gt;
                    if isinstance(timestamp, int) and not isinstance(timestamp, bool) and timestamp &amp;gt; 0:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = timestamp&lt;br /&gt;
                    elif &amp;quot;created_ns&amp;quot; not in metadata:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = _timestamp_ns(metadata.get(&amp;quot;created_utc&amp;quot;))&lt;br /&gt;
                    else:&lt;br /&gt;
                        metadata[&amp;quot;_order_ns&amp;quot;] = 0&lt;br /&gt;
                        metadata[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
                    rows.append(metadata)&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    # A manual administrator may have archived this candidate.&lt;br /&gt;
                    continue&lt;br /&gt;
                except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                    rows.append({&amp;quot;id&amp;quot;: entry.name, &amp;quot;_invalid&amp;quot;: True, &amp;quot;_order_ns&amp;quot;: 0})&lt;br /&gt;
    tied = {}&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
            tied.setdefault((row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;]), set()).add(row[&amp;quot;label&amp;quot;])&lt;br /&gt;
    for row in rows:&lt;br /&gt;
        if not row.get(&amp;quot;_invalid&amp;quot;) and len(tied[(row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;sha256&amp;quot;])]) &amp;gt; 1:&lt;br /&gt;
            row[&amp;quot;_ordering_invalid&amp;quot;] = True&lt;br /&gt;
    return sorted(rows, key=lambda row: (row[&amp;quot;_order_ns&amp;quot;], row[&amp;quot;id&amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _journal(root):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Read root-owned receipts; successful classes use durable worker sequence.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    processed = Path(root) / &amp;quot;processed&amp;quot;&lt;br /&gt;
    latest, blocked = {}, set()&lt;br /&gt;
    max_sequence = used_bytes = count = 0&lt;br /&gt;
    if not processed.exists():&lt;br /&gt;
        return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
    admin.check_root_directory(processed)&lt;br /&gt;
    with os.scandir(processed) as entries:&lt;br /&gt;
        for entry in entries:&lt;br /&gt;
            if not admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
                continue&lt;br /&gt;
            count += 1&lt;br /&gt;
            if count &amp;gt; MAX_JOURNAL_EVENTS:&lt;br /&gt;
                raise FeedbackError(&amp;quot;processed journal scan bound exceeded&amp;quot;)&lt;br /&gt;
            directory = processed / entry.name&lt;br /&gt;
            receipt = admin.receipt_for(directory)&lt;br /&gt;
            if not isinstance(receipt, dict) or receipt.get(&amp;quot;id&amp;quot;) != entry.name:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed receipt&amp;quot;)&lt;br /&gt;
            digest = receipt.get(&amp;quot;sha256&amp;quot;)&lt;br /&gt;
            if not isinstance(digest, str) or not SHA_RE.fullmatch(digest):&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed digest&amp;quot;)&lt;br /&gt;
            sequence = receipt.get(&amp;quot;worker_sequence&amp;quot;, 0)&lt;br /&gt;
            if not isinstance(sequence, int) or isinstance(sequence, bool) or sequence &amp;lt; 0:&lt;br /&gt;
                raise FeedbackError(&amp;quot;invalid processed sequence&amp;quot;)&lt;br /&gt;
            max_sequence = max(max_sequence, sequence)&lt;br /&gt;
            status = receipt.get(&amp;quot;status&amp;quot;)&lt;br /&gt;
            if status in UNCERTAIN:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
            if status == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                label = receipt.get(&amp;quot;selected_class&amp;quot;)&lt;br /&gt;
                if label not in (&amp;quot;spam&amp;quot;, &amp;quot;ham&amp;quot;):&lt;br /&gt;
                    raise FeedbackError(&amp;quot;invalid learned class&amp;quot;)&lt;br /&gt;
                # Existing manual receipts predate automatic processing; the old&lt;br /&gt;
                # admin refuses further training of a SHA once it has a receipt.&lt;br /&gt;
                fallback = _timestamp_ns(receipt.get(&amp;quot;finished_utc&amp;quot;, receipt.get(&amp;quot;reviewed_utc&amp;quot;)))&lt;br /&gt;
                position = (bool(sequence), sequence if sequence else fallback, entry.name)&lt;br /&gt;
                if digest not in latest or position &amp;gt; latest[digest][0]:&lt;br /&gt;
                    latest[digest] = (position, label)&lt;br /&gt;
            # Count snapshots and preserved originals, never recursively walk&lt;br /&gt;
            # arbitrary files in the mail-user-owned source/ directory.&lt;br /&gt;
            for part in (directory, directory / &amp;quot;source&amp;quot;):&lt;br /&gt;
                try:&lt;br /&gt;
                    with admin.Directory(part) as safe:&lt;br /&gt;
                        for name in (&amp;quot;message.eml&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;admin.json&amp;quot;):&lt;br /&gt;
                            try:&lt;br /&gt;
                                if safe.fd is not None:&lt;br /&gt;
                                    info = os.stat(name, dir_fd=safe.fd, follow_symlinks=False)&lt;br /&gt;
                                else:&lt;br /&gt;
                                    info = (part / name).lstat()&lt;br /&gt;
                                if stat.S_ISREG(info.st_mode):&lt;br /&gt;
                                    used_bytes += info.st_size&lt;br /&gt;
                            except FileNotFoundError:&lt;br /&gt;
                                continue&lt;br /&gt;
                except FileNotFoundError:&lt;br /&gt;
                    continue&lt;br /&gt;
    return latest, blocked, max_sequence, used_bytes, count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def _check_trusted_parent_chain(path):&lt;br /&gt;
    if os.name != &amp;quot;posix&amp;quot;:&lt;br /&gt;
        return&lt;br /&gt;
    for parent in Path(path).parents:&lt;br /&gt;
        link_info = parent.lstat()&lt;br /&gt;
        info = parent.stat()&lt;br /&gt;
        if (link_info.st_uid != 0 or not stat.S_ISDIR(info.st_mode)&lt;br /&gt;
                or info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
            raise FeedbackError(&amp;quot;executable parent directories must be controlled by root&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def resolve_executable(executable):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Allow FreeBSD package symlinks, but pin both operations to their target.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    source = Path(executable)&lt;br /&gt;
    if not source.is_absolute():&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc executable must be absolute&amp;quot;)&lt;br /&gt;
    source_info = source.lstat()&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and source_info.st_uid != 0:&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc entry must be owned by root&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(source)&lt;br /&gt;
    target = source.resolve(strict=True)&lt;br /&gt;
    info = target.lstat()&lt;br /&gt;
    if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be a regular file&amp;quot;)&lt;br /&gt;
    if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o022):&lt;br /&gt;
        raise FeedbackError(&amp;quot;rspamc target must be root-owned and not writable by mail users&amp;quot;)&lt;br /&gt;
    _check_trusted_parent_chain(target)&lt;br /&gt;
    return str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def controller_ready(executable, controller, password_file, timeout):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Authenticated read-only controller probe; no candidate is archived yet.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    command = [resolve_executable(executable), &amp;quot;-h&amp;quot;, controller]&lt;br /&gt;
    if password_file:&lt;br /&gt;
        secret = Path(password_file)&lt;br /&gt;
        info = secret.lstat()&lt;br /&gt;
        if not secret.is_absolute() or not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode):&lt;br /&gt;
            raise FeedbackError(&amp;quot;unsafe controller password file&amp;quot;)&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot; and (info.st_uid != 0 or info.st_mode &amp;amp; 0o077):&lt;br /&gt;
            raise FeedbackError(&amp;quot;controller password file must be root-owned mode 0600 or stricter&amp;quot;)&lt;br /&gt;
        command.extend([&amp;quot;-P&amp;quot;, str(secret)])&lt;br /&gt;
    command.append(&amp;quot;stat&amp;quot;)&lt;br /&gt;
    try:&lt;br /&gt;
        return subprocess.run(command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,&lt;br /&gt;
                              stderr=subprocess.DEVNULL, check=False, timeout=min(timeout, 5)).returncode == 0&lt;br /&gt;
    except (subprocess.TimeoutExpired, OSError):&lt;br /&gt;
        return False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def process_pending(root, *, executable=&amp;quot;/usr/local/bin/rspamc&amp;quot;, controller=&amp;quot;127.0.0.1:11334&amp;quot;,&lt;br /&gt;
                    password_file=admin.DEFAULT_PASSWORD_FILE, timeout=30, batch_size=20,&lt;br /&gt;
                    preflight=None, learner=None, stop_event=None):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Process at most batch_size candidates. Inject probes/learner for unit tests.&lt;br /&gt;
&lt;br /&gt;
    One admin lock serializes manual and background operations. A crash after a&lt;br /&gt;
    durable learning intent leaves status=learning, which blocks this SHA on the&lt;br /&gt;
    next pass; subsequent candidates are preserved as quarantined_uncertain.&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    root = Path(root)&lt;br /&gt;
    preflight = controller_ready if preflight is None else preflight&lt;br /&gt;
    if learner is None:&lt;br /&gt;
        # FreeBSD pkg installs rspamc as a root-owned versioned symlink. Resolve&lt;br /&gt;
        # it once so preflight and training invoke the same trusted executable.&lt;br /&gt;
        executable = resolve_executable(executable)&lt;br /&gt;
        learner = admin.run_learning&lt;br /&gt;
    summary = {&amp;quot;learned&amp;quot;: 0, &amp;quot;duplicates&amp;quot;: 0, &amp;quot;quarantined&amp;quot;: 0, &amp;quot;invalid&amp;quot;: 0,&lt;br /&gt;
               &amp;quot;pending&amp;quot;: 0, &amp;quot;controller_unavailable&amp;quot;: False, &amp;quot;storage_blocked&amp;quot;: False,&lt;br /&gt;
               &amp;quot;archive_warning&amp;quot;: False}&lt;br /&gt;
    with admin.admin_lock(root):&lt;br /&gt;
        events = order_events(root)&lt;br /&gt;
        summary[&amp;quot;pending&amp;quot;] = len(events)&lt;br /&gt;
        if not events:&lt;br /&gt;
            return summary&lt;br /&gt;
        latest, blocked, sequence, used_bytes, archive_count = _journal(root)&lt;br /&gt;
        summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
        if used_bytes &amp;gt;= admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
            summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        if not preflight(executable, controller, password_file, timeout):&lt;br /&gt;
            summary[&amp;quot;controller_unavailable&amp;quot;] = True&lt;br /&gt;
            return summary&lt;br /&gt;
        handled = 0&lt;br /&gt;
        for candidate in events:&lt;br /&gt;
            if handled &amp;gt;= batch_size or (stop_event is not None and stop_event.is_set()):&lt;br /&gt;
                break&lt;br /&gt;
            if candidate.get(&amp;quot;_invalid&amp;quot;):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            event_id = candidate[&amp;quot;id&amp;quot;]&lt;br /&gt;
            if (root / &amp;quot;processed&amp;quot; / event_id).exists():&lt;br /&gt;
                # Crash before prepare_archive moved pending/ID: its root-owned&lt;br /&gt;
                # intent is authoritative. Preserve the pending original for&lt;br /&gt;
                # manual recovery and continue independent content, never retry.&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            try:&lt;br /&gt;
                metadata, message = admin.read_event(root / &amp;quot;pending&amp;quot;, event_id)&lt;br /&gt;
            except FileNotFoundError:&lt;br /&gt;
                continue&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            # Do not act if the queued ordering/identity changed since enumeration.&lt;br /&gt;
            if any(metadata.get(key) != candidate.get(key) for key in&lt;br /&gt;
                   (&amp;quot;sha256&amp;quot;, &amp;quot;label&amp;quot;, &amp;quot;created_ns&amp;quot;, &amp;quot;created_utc&amp;quot;)):&lt;br /&gt;
                summary[&amp;quot;invalid&amp;quot;] += 1&lt;br /&gt;
                continue&lt;br /&gt;
            if used_bytes + 2 * len(message) + 32768 &amp;gt; admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            label, digest = metadata[&amp;quot;label&amp;quot;], metadata[&amp;quot;sha256&amp;quot;]&lt;br /&gt;
            if candidate.get(&amp;quot;_ordering_invalid&amp;quot;):&lt;br /&gt;
                status, reason = &amp;quot;quarantined_ordering&amp;quot;, &amp;quot;event ordering is ambiguous&amp;quot;&lt;br /&gt;
            elif digest in blocked:&lt;br /&gt;
                status, reason = &amp;quot;quarantined_uncertain&amp;quot;, &amp;quot;a prior learning operation has an uncertain result&amp;quot;&lt;br /&gt;
            elif digest in latest and latest[digest][1] == label:&lt;br /&gt;
                status, reason = &amp;quot;duplicate&amp;quot;, &amp;quot;same class as the latest successful learning operation&amp;quot;&lt;br /&gt;
            else:&lt;br /&gt;
                status, reason = &amp;quot;learning&amp;quot;, &amp;quot;automatic explicit IMAP feedback&amp;quot;&lt;br /&gt;
            try:&lt;br /&gt;
                archive, receipt = admin.prepare_archive(root, event_id, metadata, message, status, label, reason)&lt;br /&gt;
            except (OSError, ValueError, FeedbackError):&lt;br /&gt;
                # prepare_archive writes a root journal before moving the source;&lt;br /&gt;
                # stop instead of retrying an incompletely prepared operation.&lt;br /&gt;
                summary[&amp;quot;storage_blocked&amp;quot;] = True&lt;br /&gt;
                break&lt;br /&gt;
            sequence += 1&lt;br /&gt;
            receipt.update({&amp;quot;mode&amp;quot;: &amp;quot;automatic_imap_feedback&amp;quot;, &amp;quot;worker_sequence&amp;quot;: sequence,&lt;br /&gt;
                            &amp;quot;processed_ns&amp;quot;: time.time_ns(), &amp;quot;source_created_ns&amp;quot;: candidate[&amp;quot;_order_ns&amp;quot;]})&lt;br /&gt;
            if digest in latest:&lt;br /&gt;
                receipt[&amp;quot;previous_successful_class&amp;quot;] = latest[digest][1]&lt;br /&gt;
            # Durable intent exists before the only call that may change Bayes.&lt;br /&gt;
            admin.update_receipt(archive, receipt)&lt;br /&gt;
            if status == &amp;quot;learning&amp;quot;:&lt;br /&gt;
                try:&lt;br /&gt;
                    returncode = learner(message, label, executable=executable, controller=controller,&lt;br /&gt;
                                         password_file=password_file, timeout=timeout)&lt;br /&gt;
                except (subprocess.TimeoutExpired, OSError, FeedbackError):&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    receipt[&amp;quot;error&amp;quot;] = &amp;quot;client failure; server may have learned; no automatic retry&amp;quot;&lt;br /&gt;
                else:&lt;br /&gt;
                    receipt[&amp;quot;returncode&amp;quot;] = returncode&lt;br /&gt;
                    receipt[&amp;quot;status&amp;quot;] = &amp;quot;learned&amp;quot; if returncode == 0 else &amp;quot;learn_uncertain&amp;quot;&lt;br /&gt;
                    if returncode != 0:&lt;br /&gt;
                        receipt[&amp;quot;error&amp;quot;] = &amp;quot;client returned failure; no automatic retry&amp;quot;&lt;br /&gt;
                receipt[&amp;quot;finished_utc&amp;quot;] = admin.collector.utc_now()&lt;br /&gt;
                admin.update_receipt(archive, receipt)&lt;br /&gt;
                if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learned&amp;quot;:&lt;br /&gt;
                    latest[digest] = ((True, sequence, event_id), label)&lt;br /&gt;
                    summary[&amp;quot;learned&amp;quot;] += 1&lt;br /&gt;
                else:&lt;br /&gt;
                    blocked.add(digest)&lt;br /&gt;
                    summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            elif status == &amp;quot;duplicate&amp;quot;:&lt;br /&gt;
                summary[&amp;quot;duplicates&amp;quot;] += 1&lt;br /&gt;
            else:&lt;br /&gt;
                blocked.add(digest)&lt;br /&gt;
                summary[&amp;quot;quarantined&amp;quot;] += 1&lt;br /&gt;
            handled += 1&lt;br /&gt;
            used_bytes += 2 * len(message) + 32768&lt;br /&gt;
            archive_count += 1&lt;br /&gt;
            summary[&amp;quot;pending&amp;quot;] -= 1&lt;br /&gt;
            summary[&amp;quot;archive_warning&amp;quot;] = archive_count &amp;gt;= admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
            log_event(&amp;quot;feedback_processed&amp;quot;, id=event_id, label=label, status=receipt[&amp;quot;status&amp;quot;])&lt;br /&gt;
            if receipt[&amp;quot;status&amp;quot;] == &amp;quot;learn_uncertain&amp;quot;:&lt;br /&gt;
                # The controller may have failed after the preflight. Leave all&lt;br /&gt;
                # other candidates pending until a fresh read-only probe passes.&lt;br /&gt;
                break&lt;br /&gt;
    return summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main(argv=None):&lt;br /&gt;
    parser = argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
    parser.add_argument(&amp;quot;--queue-root&amp;quot;, default=admin.DEFAULT_ROOT)&lt;br /&gt;
    parser.add_argument(&amp;quot;--rspamc-bin&amp;quot;, default=&amp;quot;/usr/local/bin/rspamc&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--controller&amp;quot;, default=&amp;quot;127.0.0.1:11334&amp;quot;)&lt;br /&gt;
    parser.add_argument(&amp;quot;--password-file&amp;quot;, default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
    parser.add_argument(&amp;quot;--poll&amp;quot;, type=float, default=2)&lt;br /&gt;
    parser.add_argument(&amp;quot;--timeout&amp;quot;, type=float, default=30)&lt;br /&gt;
    parser.add_argument(&amp;quot;--batch-size&amp;quot;, type=int, default=20)&lt;br /&gt;
    parser.add_argument(&amp;quot;--once&amp;quot;, action=&amp;quot;store_true&amp;quot;)&lt;br /&gt;
    args = parser.parse_args(argv)&lt;br /&gt;
    os.umask(0o077)&lt;br /&gt;
    admin.require_root()&lt;br /&gt;
    root = Path(args.queue_root)&lt;br /&gt;
    if not root.is_absolute() or not 0.2 &amp;lt;= args.poll &amp;lt;= 60 or not 0 &amp;lt; args.timeout &amp;lt;= 300 or not 1 &amp;lt;= args.batch_size &amp;lt;= 100:&lt;br /&gt;
        raise FeedbackError(&amp;quot;invalid worker options&amp;quot;)&lt;br /&gt;
    admin.check_root_directory(root)&lt;br /&gt;
    stopping = threading.Event()&lt;br /&gt;
    for signum in (signal.SIGTERM, signal.SIGINT):&lt;br /&gt;
        signal.signal(signum, lambda *_: stopping.set())&lt;br /&gt;
    lockfd = os.open(root / &amp;quot;.worker.lock&amp;quot;, os.O_RDWR | os.O_CREAT | getattr(os, &amp;quot;O_NOFOLLOW&amp;quot;, 0), 0o600)&lt;br /&gt;
    try:&lt;br /&gt;
        if os.name == &amp;quot;posix&amp;quot;:&lt;br /&gt;
            import fcntl&lt;br /&gt;
            fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)&lt;br /&gt;
        previous_state = None&lt;br /&gt;
        while not stopping.is_set():&lt;br /&gt;
            try:&lt;br /&gt;
                summary = process_pending(root, executable=args.rspamc_bin, controller=args.controller,&lt;br /&gt;
                                          password_file=args.password_file, timeout=args.timeout,&lt;br /&gt;
                                          batch_size=args.batch_size, stop_event=stopping)&lt;br /&gt;
                state = {key: value for key, value in summary.items() if key not in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)}&lt;br /&gt;
                if state != previous_state or any(summary[key] for key in (&amp;quot;learned&amp;quot;, &amp;quot;duplicates&amp;quot;, &amp;quot;quarantined&amp;quot;)):&lt;br /&gt;
                    log_event(&amp;quot;worker_state&amp;quot;, **summary)&lt;br /&gt;
                previous_state = state&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 2 if summary[&amp;quot;controller_unavailable&amp;quot;] or summary[&amp;quot;storage_blocked&amp;quot;] else 0&lt;br /&gt;
            except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
                if previous_state != &amp;quot;local_error&amp;quot;:&lt;br /&gt;
                    log_event(&amp;quot;worker_error&amp;quot;, reason=&amp;quot;local queue, journal or configuration failure; no automatic retry of uncertain training&amp;quot;)&lt;br /&gt;
                previous_state = &amp;quot;local_error&amp;quot;&lt;br /&gt;
                if args.once:&lt;br /&gt;
                    return 1&lt;br /&gt;
            stopping.wait(args.poll)&lt;br /&gt;
        return 0&lt;br /&gt;
    finally:&lt;br /&gt;
        os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    try:&lt;br /&gt;
        raise SystemExit(main())&lt;br /&gt;
    except (OSError, ValueError, TypeError, FeedbackError):&lt;br /&gt;
        log_event(&amp;quot;worker_start_failed&amp;quot;, reason=&amp;quot;root, configuration or worker lock required&amp;quot;)&lt;br /&gt;
        raise SystemExit(1)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=684</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=684"/>
		<updated>2026-10-29T23:47:20Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 1. Схема и требования */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет → TCP 25 / STARTTLS → Exim&lt;br /&gt;
Клиент   → TCP 465 TLS или 587 STARTTLS + AUTH → Exim&lt;br /&gt;
Exim     → LMTP Unix socket → Dovecot → /opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim     → AUTH Unix socket → Dovecot → файловый каталог пользователей&lt;br /&gt;
Клиент   → TCP 993 TLS → Dovecot&lt;br /&gt;
SnappyMail (необязательно) → те же IMAPS и SMTP AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
sysrc hostname=mail.example.org&lt;br /&gt;
hostname mail.example.org&lt;br /&gt;
sysrc defaultrouter=198.51.100.1&lt;br /&gt;
# Только если в работающей таблице отсутствует default route:&lt;br /&gt;
route add default 198.51.100.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo /dev/da1 | awk &#039;{print $3}&#039;)&lt;br /&gt;
test -n &amp;quot;$disk_bytes&amp;quot; &amp;amp;&amp;amp; test &amp;quot;$disk_bytes&amp;quot; -gt 0 || exit 1&lt;br /&gt;
cmp -s -n &amp;quot;$disk_bytes&amp;quot; /dev/da1 /dev/zero&lt;br /&gt;
printf &#039;cmp status: %s\n&#039; &amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
pkg update&lt;br /&gt;
pkg install -n exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
# После проверки плана:&lt;br /&gt;
pkg install -y exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
sysrc exim_enable=NO dovecot_enable=NO&lt;br /&gt;
sysrc sendmail_enable=NONE sendmail_submit_enable=NO&lt;br /&gt;
sysrc sendmail_outbound_enable=NO sendmail_msp_queue_enable=NO&lt;br /&gt;
service sendmail onestop&lt;br /&gt;
/usr/local/sbin/exim -bV&lt;br /&gt;
/usr/local/sbin/dovecot --version&lt;br /&gt;
pw usershow mailnull&lt;br /&gt;
pw groupshow mail&lt;br /&gt;
pw groupshow dovecot&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;review&amp;amp;#32;and&amp;amp;#32;explicit&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback.&lt;br /&gt;
&lt;br /&gt;
No&amp;amp;#32;command&amp;amp;#32;trains&amp;amp;#32;automatically.&amp;amp;#32;list/show&amp;amp;#32;are&amp;amp;#32;local&amp;amp;#32;reads.&amp;amp;#32;learn&amp;amp;#32;ID&amp;amp;#32;spam|ham&lt;br /&gt;
is&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;command&amp;amp;#32;that&amp;amp;#32;invokes&amp;amp;#32;rspamc.&amp;amp;#32;reject&amp;amp;#32;ID&amp;amp;#32;archives&amp;amp;#32;without&amp;amp;#32;training.&lt;br /&gt;
Queue&amp;amp;#32;root&amp;amp;#32;and&amp;amp;#32;processed&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;non-writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users;&lt;br /&gt;
pending&amp;amp;#32;is&amp;amp;#32;separately&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;vmail,&amp;amp;#32;mode&amp;amp;#32;0700.&amp;amp;#32;Archived&amp;amp;#32;originals&amp;amp;#32;remain&amp;amp;#32;in&lt;br /&gt;
processed/ID/source;&amp;amp;#32;only&amp;amp;#32;root-owned&amp;amp;#32;snapshots&amp;amp;#32;are&amp;amp;#32;used&amp;amp;#32;for&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;review.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;email&amp;amp;#32;import&amp;amp;#32;policy&lt;br /&gt;
from&amp;amp;#32;email.parser&amp;amp;#32;import&amp;amp;#32;BytesHeaderParser&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
&lt;br /&gt;
spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_collector&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback.py&amp;amp;quot;))&lt;br /&gt;
collector&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;collector.FeedbackError&lt;br /&gt;
ID_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{32}\Z&amp;amp;quot;)&lt;br /&gt;
DEFAULT_ROOT&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback&amp;amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/usr/local/etc/rspamd/controller.password&amp;amp;quot;&lt;br /&gt;
HEADERS&amp;amp;#32;=&amp;amp;#32;(&amp;amp;quot;From&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;To&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Date&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Subject&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Message-ID&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Status&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Score&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Rspamd-Action&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;emit(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;require_root():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;hasattr(os,&amp;amp;#32;&amp;amp;quot;geteuid&amp;amp;quot;)&amp;amp;#32;or&amp;amp;#32;os.geteuid()&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;administrative&amp;amp;#32;command&amp;amp;#32;requires&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_id(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;ID&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;exactly&amp;amp;#32;32&amp;amp;#32;lowercase&amp;amp;#32;hexadecimal&amp;amp;#32;characters&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;Directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Nofollow&amp;amp;#32;directory&amp;amp;#32;descriptor&amp;amp;#32;on&amp;amp;#32;FreeBSD;&amp;amp;#32;portable&amp;amp;#32;fallback&amp;amp;#32;for&amp;amp;#32;local&amp;amp;#32;tests.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__init__(self,&amp;amp;#32;path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;self.path.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;expected&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;os.open(self.path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__enter__(self):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;self&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__exit__(self,&amp;amp;#32;*args):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;read(self,&amp;amp;#32;name,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;&amp;amp;quot;\\&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;.&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;..&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;internal&amp;amp;#32;filename&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(name,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW&amp;amp;#32;|&amp;amp;#32;os.O_NONBLOCK,&amp;amp;#32;dir_fd=self.fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;self.path&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;symlink&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;event&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;rb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;source:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.fstat(source.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;info.st_nlink&amp;amp;#32;!=&amp;amp;#32;1&amp;amp;#32;or&amp;amp;#32;info.st_size&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;bounded,&amp;amp;#32;single-link&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;source.read(limit&amp;amp;#32;+&amp;amp;#32;1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(result)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;exceeded&amp;amp;#32;the&amp;amp;#32;size&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;read_event(directory,&amp;amp;#32;event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;On&amp;amp;#32;POSIX&amp;amp;#32;open&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;relative&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;already&amp;amp;#32;opened&amp;amp;#32;parent&amp;amp;#32;descriptor.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;parent&amp;amp;#32;path&amp;amp;#32;is&amp;amp;#32;deployment-owned,&amp;amp;#32;and&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;event&amp;amp;#32;is&amp;amp;#32;rejected.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(Path(directory)&amp;amp;#32;/&amp;amp;#32;event_id)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;entry.read(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;MAX_MESSAGE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;user&amp;amp;quot;),&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;event_id&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;bytes&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;len(message)&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;hashlib.sha256(message).hexdigest():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;content&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;its&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata,&amp;amp;#32;message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write_private(path,&amp;amp;#32;value,&amp;amp;#32;*,&amp;amp;#32;json_value=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;flags,&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;json_value:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;value&amp;amp;#32;=&amp;amp;#32;(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;\n&amp;amp;quot;).encode()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(value)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;update_receipt(directory,&amp;amp;#32;receipt):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin-new.json&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(temporary,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.replace(temporary,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_root_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;Path(path).stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;administrative&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;group/other&amp;amp;#32;writable&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;receipt_for(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;find_prior(root,&amp;amp;#32;sha256):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;damaged&amp;amp;#32;journal&amp;amp;#32;must&amp;amp;#32;never&amp;amp;#32;silently&amp;amp;#32;allow&amp;amp;#32;repeated&amp;amp;#32;training.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;is&amp;amp;#32;incomplete;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;before&amp;amp;#32;more&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;==&amp;amp;#32;sha256&amp;amp;#32;and&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label=None,&amp;amp;#32;reason=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(root).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;collector.MIN_FREE_BYTES&amp;amp;#32;+&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;#32;to&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;reviewed&amp;amp;#32;snapshot&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;],&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;reviewed_utc&amp;amp;quot;:&amp;amp;#32;collector.utc_now(),&amp;amp;#32;&amp;amp;quot;status&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;preparing&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;selected_class&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;reason&amp;amp;quot;:&amp;amp;#32;reason}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;json.dumps(metadata,&amp;amp;#32;ensure_ascii=True).encode())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;complete&amp;amp;#32;original&amp;amp;#32;event.&amp;amp;#32;Never&amp;amp;#32;traverse&amp;amp;#32;or&amp;amp;#32;execute&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;source/.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id,&amp;amp;#32;archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;status&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;archive,&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;executable.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;path&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[str(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;rspamc&amp;amp;#32;reads&amp;amp;#32;the&amp;amp;#32;file&amp;amp;#32;itself;&amp;amp;#32;the&amp;amp;#32;secret&amp;amp;#32;never&amp;amp;#32;appears&amp;amp;#32;in&amp;amp;#32;argv&amp;amp;#32;or&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;learn_&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;-u/-d:&amp;amp;#32;one&amp;amp;#32;shared&amp;amp;#32;Bayes&amp;amp;#32;classifier.&amp;amp;#32;No&amp;amp;#32;shell&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;input=message,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;timeout=timeout,&amp;amp;#32;check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;learn_event(root,&amp;amp;#32;event_id,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;training&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;prior&amp;amp;#32;=&amp;amp;#32;find_prior(root,&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prior:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;prior&amp;amp;#32;learned&amp;amp;#32;or&amp;amp;#32;uncertain&amp;amp;#32;event;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;and&amp;amp;#32;reject&amp;amp;#32;the&amp;amp;#32;duplicate&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rc&amp;amp;#32;=&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;subprocess.TimeoutExpired:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;timeout;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;automatically&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_failed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;could&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;or&amp;amp;#32;local&amp;amp;#32;configuration&amp;amp;#32;was&amp;amp;#32;invalid&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;rc&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;rc&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;rc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;inspect&amp;amp;#32;server&amp;amp;#32;before&amp;amp;#32;any&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;reject_event(root,&amp;amp;#32;event_id,&amp;amp;#32;reason=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(reason)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;500:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;reason&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;rejected&amp;amp;quot;,&amp;amp;#32;reason=reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;list_events(root,&amp;amp;#32;state,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;((&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;(state,)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;directory.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;directory.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;notice&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;limit&amp;amp;#32;reached&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;limit&amp;amp;quot;:&amp;amp;#32;limit})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(entry)&amp;amp;#32;as&amp;amp;#32;item:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(item.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;metadata.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;id&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;state&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;review&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(row)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;state&amp;amp;quot;:&amp;amp;#32;name,&amp;amp;#32;&amp;amp;quot;error&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;#32;event;&amp;amp;#32;manual&amp;amp;#32;inspection&amp;amp;#32;required&amp;amp;quot;})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;show_event(root,&amp;amp;#32;event_id,&amp;amp;#32;state,&amp;amp;#32;path_only):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;state,&amp;amp;#32;check_id(event_id))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path_only:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;message_path&amp;amp;quot;:&amp;amp;#32;str(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;)})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;headers&amp;amp;#32;=&amp;amp;#32;BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message),&amp;amp;#32;262144)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;metadata&amp;amp;quot;:&amp;amp;#32;metadata,&amp;amp;#32;&amp;amp;quot;headers&amp;amp;quot;:&amp;amp;#32;{name:&amp;amp;#32;[str(v)[:2000]&amp;amp;#32;for&amp;amp;#32;v&amp;amp;#32;in&amp;amp;#32;headers.get_all(name,&amp;amp;#32;[])[:10]]&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;HEADERS}})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;review&amp;amp;quot;:&amp;amp;#32;receipt_for(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;warn_processed_storage(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;event&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event.name)&amp;amp;#32;or&amp;amp;#32;event.is_symlink()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;event.is_dir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;prefix&amp;amp;#32;in&amp;amp;#32;(event,&amp;amp;#32;event&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prefix.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(prefix&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_BYTES&amp;amp;#32;or&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;WARNING&amp;amp;#32;processed&amp;amp;#32;archive&amp;amp;#32;reached&amp;amp;#32;512&amp;amp;#32;MiB&amp;amp;#32;or&amp;amp;#32;1024&amp;amp;#32;events;&amp;amp;#32;review&amp;amp;#32;retention&amp;amp;#32;manually;&amp;amp;#32;nothing&amp;amp;#32;deleted&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sub&amp;amp;#32;=&amp;amp;#32;parser.add_subparsers(dest=&amp;amp;quot;command&amp;amp;quot;,&amp;amp;#32;required=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;list&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;JSON&amp;amp;#32;lines,&amp;amp;#32;metadata&amp;amp;#32;only&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--limit&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=100)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;show&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;selected&amp;amp;#32;headers&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;dumps&amp;amp;#32;the&amp;amp;#32;body&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--path&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;learn&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;explicitly&amp;amp;#32;train&amp;amp;#32;one&amp;amp;#32;reviewed&amp;amp;#32;event,&amp;amp;#32;then&amp;amp;#32;archive&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;help=&amp;amp;quot;root-only&amp;amp;#32;plaintext&amp;amp;#32;secret&amp;amp;#32;file;&amp;amp;#32;use&amp;amp;#32;--no-password&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;an&amp;amp;#32;intentionally&amp;amp;#32;trusted&amp;amp;#32;local&amp;amp;#32;controller&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--no-password&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;reject&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;archive&amp;amp;#32;one&amp;amp;#32;event&amp;amp;#32;without&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;deleting&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;--reason&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;root&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn_processed_storage(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;list&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.limit&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;10000:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;limit&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;1&amp;amp;#32;and&amp;amp;#32;10000&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;list_events(root,&amp;amp;#32;args.state,&amp;amp;#32;args.limit)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;show&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;show_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.state,&amp;amp;#32;args.path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;reject&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(reject_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.reason))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;timeout&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;0&amp;amp;#32;and&amp;amp;#32;300&amp;amp;#32;seconds&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;learn_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.label,&amp;amp;#32;executable=args.rspamc_bin,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;controller=args.controller,&amp;amp;#32;password_file=None&amp;amp;#32;if&amp;amp;#32;args.no_password&amp;amp;#32;else&amp;amp;#32;args.password_file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timeout=args.timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(result)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&amp;amp;#32;if&amp;amp;#32;result[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;operation&amp;amp;#32;refused&amp;amp;#32;or&amp;amp;#32;failed;&amp;amp;#32;inspect&amp;amp;#32;configuration/event&amp;amp;#32;locally&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;background&amp;amp;#32;learning&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;bounded&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback&amp;amp;#32;queue.&lt;br /&gt;
&lt;br /&gt;
The&amp;amp;#32;collector&amp;amp;#32;remains&amp;amp;#32;a&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;writer.&amp;amp;#32;This&amp;amp;#32;process&amp;amp;#32;owns&amp;amp;#32;controller&lt;br /&gt;
credentials,&amp;amp;#32;learns&amp;amp;#32;only&amp;amp;#32;validated&amp;amp;#32;INBOX/Junk&amp;amp;#32;events,&amp;amp;#32;preserves&amp;amp;#32;every&amp;amp;#32;original,&lt;br /&gt;
and&amp;amp;#32;never&amp;amp;#32;retries&amp;amp;#32;a&amp;amp;#32;training&amp;amp;#32;operation&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result.&amp;amp;#32;Run&amp;amp;#32;foreground&lt;br /&gt;
under&amp;amp;#32;FreeBSD&amp;amp;#32;daemon(8),&amp;amp;#32;or&amp;amp;#32;use&amp;amp;#32;--once&amp;amp;#32;for&amp;amp;#32;one&amp;amp;#32;bounded&amp;amp;#32;batch.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;signal&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;threading&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
&lt;br /&gt;
_spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_worker_admin&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback-admin.py&amp;amp;quot;))&lt;br /&gt;
admin&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;admin.FeedbackError&lt;br /&gt;
SHA_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{64}\Z&amp;amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
UNCERTAIN&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;log_event(kind,&amp;amp;#32;**fields):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Callers&amp;amp;#32;pass&amp;amp;#32;only&amp;amp;#32;fixed&amp;amp;#32;diagnostics,&amp;amp;#32;numeric&amp;amp;#32;counts,&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;classes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps({&amp;amp;quot;event&amp;amp;quot;:&amp;amp;#32;kind,&amp;amp;#32;**fields},&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True),&amp;amp;#32;flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_timestamp_ns(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(value,&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;missing&amp;amp;#32;timestamp&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parsed&amp;amp;#32;=&amp;amp;#32;datetime.fromisoformat(value.replace(&amp;amp;quot;Z&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;+00:00&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;parsed.tzinfo&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;timestamp&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;timezone&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;int(parsed.timestamp()&amp;amp;#32;*&amp;amp;#32;1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;order_events(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;bounded&amp;amp;#32;metadata,&amp;amp;#32;ordered&amp;amp;#32;by&amp;amp;#32;collector&amp;amp;#32;created_ns,&amp;amp;#32;then&amp;amp;#32;event&amp;amp;#32;ID.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;Legacy&amp;amp;#32;second-resolution&amp;amp;#32;timestamps&amp;amp;#32;are&amp;amp;#32;permitted&amp;amp;#32;only&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;same&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;contradictory&amp;amp;#32;labels&amp;amp;#32;at&amp;amp;#32;an&amp;amp;#32;identical&amp;amp;#32;timestamp.&amp;amp;#32;Such&amp;amp;#32;a&amp;amp;#32;tie&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cannot&amp;amp;#32;be&amp;amp;#32;resolved&amp;amp;#32;from&amp;amp;#32;random&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;is&amp;amp;#32;quarantined&amp;amp;#32;without&amp;amp;#32;learning.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pending&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows&amp;amp;#32;=&amp;amp;#32;[]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(pending)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;queue&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending&amp;amp;#32;/&amp;amp;#32;entry.name)&amp;amp;#32;as&amp;amp;#32;directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(directory.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;dict(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;sha256&amp;amp;quot;),&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(metadata[&amp;amp;quot;sha256&amp;amp;quot;]):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timestamp&amp;amp;#32;=&amp;amp;#32;metadata.get(&amp;amp;quot;created_ns&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;int)&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;bool)&amp;amp;#32;and&amp;amp;#32;timestamp&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;timestamp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;metadata:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(metadata.get(&amp;amp;quot;created_utc&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;manual&amp;amp;#32;administrator&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;archived&amp;amp;#32;this&amp;amp;#32;candidate.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;_invalid&amp;amp;quot;:&amp;amp;#32;True,&amp;amp;#32;&amp;amp;quot;_order_ns&amp;amp;quot;:&amp;amp;#32;0})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied&amp;amp;#32;=&amp;amp;#32;{}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied.setdefault((row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;]),&amp;amp;#32;set()).add(row[&amp;amp;quot;label&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;)&amp;amp;#32;and&amp;amp;#32;len(tied[(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;])])&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;sorted(rows,&amp;amp;#32;key=lambda&amp;amp;#32;row:&amp;amp;#32;(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;id&amp;amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_journal(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;root-owned&amp;amp;#32;receipts;&amp;amp;#32;successful&amp;amp;#32;classes&amp;amp;#32;use&amp;amp;#32;durable&amp;amp;#32;worker&amp;amp;#32;sequence.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked&amp;amp;#32;=&amp;amp;#32;{},&amp;amp;#32;set()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;used_bytes&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(processed)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;MAX_JOURNAL_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;processed&amp;amp;#32;/&amp;amp;#32;entry.name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.receipt_for(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(receipt,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;receipt.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;receipt&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(digest,&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(digest):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;worker_sequence&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(sequence,&amp;amp;#32;int)&amp;amp;#32;or&amp;amp;#32;isinstance(sequence,&amp;amp;#32;bool)&amp;amp;#32;or&amp;amp;#32;sequence&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;sequence&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;max(max_sequence,&amp;amp;#32;sequence)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;in&amp;amp;#32;UNCERTAIN:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;selected_class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;learned&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Existing&amp;amp;#32;manual&amp;amp;#32;receipts&amp;amp;#32;predate&amp;amp;#32;automatic&amp;amp;#32;processing;&amp;amp;#32;the&amp;amp;#32;old&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;admin&amp;amp;#32;refuses&amp;amp;#32;further&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;a&amp;amp;#32;SHA&amp;amp;#32;once&amp;amp;#32;it&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;receipt.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fallback&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(receipt.get(&amp;amp;quot;finished_utc&amp;amp;quot;,&amp;amp;#32;receipt.get(&amp;amp;quot;reviewed_utc&amp;amp;quot;)))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;position&amp;amp;#32;=&amp;amp;#32;(bool(sequence),&amp;amp;#32;sequence&amp;amp;#32;if&amp;amp;#32;sequence&amp;amp;#32;else&amp;amp;#32;fallback,&amp;amp;#32;entry.name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;or&amp;amp;#32;position&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;latest[digest][0]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;(position,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Count&amp;amp;#32;snapshots&amp;amp;#32;and&amp;amp;#32;preserved&amp;amp;#32;originals,&amp;amp;#32;never&amp;amp;#32;recursively&amp;amp;#32;walk&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;arbitrary&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail-user-owned&amp;amp;#32;source/&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;part&amp;amp;#32;in&amp;amp;#32;(directory,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(part)&amp;amp;#32;as&amp;amp;#32;safe:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;safe.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.stat(name,&amp;amp;#32;dir_fd=safe.fd,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(part&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_check_trusted_parent_chain(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;parent&amp;amp;#32;in&amp;amp;#32;Path(path).parents:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;link_info&amp;amp;#32;=&amp;amp;#32;parent.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;parent.stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(link_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;or&amp;amp;#32;info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;executable&amp;amp;#32;parent&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;controlled&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;resolve_executable(executable):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Allow&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;symlinks,&amp;amp;#32;but&amp;amp;#32;pin&amp;amp;#32;both&amp;amp;#32;operations&amp;amp;#32;to&amp;amp;#32;their&amp;amp;#32;target.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;source.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source_info&amp;amp;#32;=&amp;amp;#32;source.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;source_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;entry&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(source)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target&amp;amp;#32;=&amp;amp;#32;source.resolve(strict=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;target.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(target)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;controller_ready(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Authenticated&amp;amp;#32;read-only&amp;amp;#32;controller&amp;amp;#32;probe;&amp;amp;#32;no&amp;amp;#32;candidate&amp;amp;#32;is&amp;amp;#32;archived&amp;amp;#32;yet.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[resolve_executable(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unsafe&amp;amp;#32;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;stat&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;stdin=subprocess.DEVNULL,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;check=False,&amp;amp;#32;timeout=min(timeout,&amp;amp;#32;5)).returncode&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;process_pending(root,&amp;amp;#32;*,&amp;amp;#32;executable=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;,&amp;amp;#32;controller=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=admin.DEFAULT_PASSWORD_FILE,&amp;amp;#32;timeout=30,&amp;amp;#32;batch_size=20,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight=None,&amp;amp;#32;learner=None,&amp;amp;#32;stop_event=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Process&amp;amp;#32;at&amp;amp;#32;most&amp;amp;#32;batch_size&amp;amp;#32;candidates.&amp;amp;#32;Inject&amp;amp;#32;probes/learner&amp;amp;#32;for&amp;amp;#32;unit&amp;amp;#32;tests.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;One&amp;amp;#32;admin&amp;amp;#32;lock&amp;amp;#32;serializes&amp;amp;#32;manual&amp;amp;#32;and&amp;amp;#32;background&amp;amp;#32;operations.&amp;amp;#32;A&amp;amp;#32;crash&amp;amp;#32;after&amp;amp;#32;a&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;durable&amp;amp;#32;learning&amp;amp;#32;intent&amp;amp;#32;leaves&amp;amp;#32;status=learning,&amp;amp;#32;which&amp;amp;#32;blocks&amp;amp;#32;this&amp;amp;#32;SHA&amp;amp;#32;on&amp;amp;#32;the&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;next&amp;amp;#32;pass;&amp;amp;#32;subsequent&amp;amp;#32;candidates&amp;amp;#32;are&amp;amp;#32;preserved&amp;amp;#32;as&amp;amp;#32;quarantined_uncertain.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight&amp;amp;#32;=&amp;amp;#32;controller_ready&amp;amp;#32;if&amp;amp;#32;preflight&amp;amp;#32;is&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;preflight&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;learner&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;pkg&amp;amp;#32;installs&amp;amp;#32;rspamc&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;root-owned&amp;amp;#32;versioned&amp;amp;#32;symlink.&amp;amp;#32;Resolve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;it&amp;amp;#32;once&amp;amp;#32;so&amp;amp;#32;preflight&amp;amp;#32;and&amp;amp;#32;training&amp;amp;#32;invoke&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;trusted&amp;amp;#32;executable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;resolve_executable(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learner&amp;amp;#32;=&amp;amp;#32;admin.run_learning&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;learned&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;quot;:&amp;amp;#32;0,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;controller_unavailable&amp;amp;quot;:&amp;amp;#32;False,&amp;amp;#32;&amp;amp;quot;storage_blocked&amp;amp;quot;:&amp;amp;#32;False,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;archive_warning&amp;amp;quot;:&amp;amp;#32;False}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;events&amp;amp;#32;=&amp;amp;#32;order_events(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;len(events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;archive_count&amp;amp;#32;=&amp;amp;#32;_journal(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;preflight(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;candidate&amp;amp;#32;in&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;handled&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;batch_size&amp;amp;#32;or&amp;amp;#32;(stop_event&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;and&amp;amp;#32;stop_event.is_set()):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;candidate[&amp;amp;quot;id&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id).exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Crash&amp;amp;#32;before&amp;amp;#32;prepare_archive&amp;amp;#32;moved&amp;amp;#32;pending/ID:&amp;amp;#32;its&amp;amp;#32;root-owned&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;intent&amp;amp;#32;is&amp;amp;#32;authoritative.&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;pending&amp;amp;#32;original&amp;amp;#32;for&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;manual&amp;amp;#32;recovery&amp;amp;#32;and&amp;amp;#32;continue&amp;amp;#32;independent&amp;amp;#32;content,&amp;amp;#32;never&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;admin.read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;act&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;queued&amp;amp;#32;ordering/identity&amp;amp;#32;changed&amp;amp;#32;since&amp;amp;#32;enumeration.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;any(metadata.get(key)&amp;amp;#32;!=&amp;amp;#32;candidate.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(&amp;amp;quot;sha256&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;+&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label,&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;metadata[&amp;amp;quot;label&amp;amp;quot;],&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_ordering_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;event&amp;amp;#32;ordering&amp;amp;#32;is&amp;amp;#32;ambiguous&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;blocked:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;a&amp;amp;#32;prior&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;#32;has&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;and&amp;amp;#32;latest[digest][1]&amp;amp;#32;==&amp;amp;#32;label:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;same&amp;amp;#32;class&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;latest&amp;amp;#32;successful&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;automatic&amp;amp;#32;explicit&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label,&amp;amp;#32;reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;prepare_archive&amp;amp;#32;writes&amp;amp;#32;a&amp;amp;#32;root&amp;amp;#32;journal&amp;amp;#32;before&amp;amp;#32;moving&amp;amp;#32;the&amp;amp;#32;source;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;stop&amp;amp;#32;instead&amp;amp;#32;of&amp;amp;#32;retrying&amp;amp;#32;an&amp;amp;#32;incompletely&amp;amp;#32;prepared&amp;amp;#32;operation.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt.update({&amp;amp;quot;mode&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;automatic_imap_feedback&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;worker_sequence&amp;amp;quot;:&amp;amp;#32;sequence,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;processed_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&amp;amp;#32;&amp;amp;quot;source_created_ns&amp;amp;quot;:&amp;amp;#32;candidate[&amp;amp;quot;_order_ns&amp;amp;quot;]})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;previous_successful_class&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;latest[digest][1]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Durable&amp;amp;#32;intent&amp;amp;#32;exists&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;call&amp;amp;#32;that&amp;amp;#32;may&amp;amp;#32;change&amp;amp;#32;Bayes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;returncode&amp;amp;#32;=&amp;amp;#32;learner(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;failure;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;returncode&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;admin.collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;((True,&amp;amp;#32;sequence,&amp;amp;#32;event_id),&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;learned&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;duplicates&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive_count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;-=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;feedback_processed&amp;amp;quot;,&amp;amp;#32;id=event_id,&amp;amp;#32;label=label,&amp;amp;#32;status=receipt[&amp;amp;quot;status&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;controller&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;failed&amp;amp;#32;after&amp;amp;#32;the&amp;amp;#32;preflight.&amp;amp;#32;Leave&amp;amp;#32;all&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;other&amp;amp;#32;candidates&amp;amp;#32;pending&amp;amp;#32;until&amp;amp;#32;a&amp;amp;#32;fresh&amp;amp;#32;read-only&amp;amp;#32;probe&amp;amp;#32;passes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--poll&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--batch-size&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=20)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--once&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0.2&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.poll&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;60&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.batch_size&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;100:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;worker&amp;amp;#32;options&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping&amp;amp;#32;=&amp;amp;#32;threading.Event()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;signum&amp;amp;#32;in&amp;amp;#32;(signal.SIGTERM,&amp;amp;#32;signal.SIGINT):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;signal.signal(signum,&amp;amp;#32;lambda&amp;amp;#32;*_:&amp;amp;#32;stopping.set())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;lockfd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.worker.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(lockfd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;not&amp;amp;#32;stopping.is_set():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;process_pending(root,&amp;amp;#32;executable=args.rspamc_bin,&amp;amp;#32;controller=args.controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=args.password_file,&amp;amp;#32;timeout=args.timeout,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;batch_size=args.batch_size,&amp;amp;#32;stop_event=stopping)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;state&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;value&amp;amp;#32;for&amp;amp;#32;key,&amp;amp;#32;value&amp;amp;#32;in&amp;amp;#32;summary.items()&amp;amp;#32;if&amp;amp;#32;key&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;!=&amp;amp;#32;previous_state&amp;amp;#32;or&amp;amp;#32;any(summary[key]&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_state&amp;amp;quot;,&amp;amp;#32;**summary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;state&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;2&amp;amp;#32;if&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;or&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;else&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;previous_state&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_error&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;local&amp;amp;#32;queue,&amp;amp;#32;journal&amp;amp;#32;or&amp;amp;#32;configuration&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;#32;of&amp;amp;#32;uncertain&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping.wait(args.poll)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_start_failed&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;root,&amp;amp;#32;configuration&amp;amp;#32;or&amp;amp;#32;worker&amp;amp;#32;lock&amp;amp;#32;required&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(1)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=683</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=683"/>
		<updated>2026-10-29T23:28:21Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 4. Пакеты, пользователи и каталоги */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;25&amp;amp;#32;/&amp;amp;#32;STARTTLS&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;465&amp;amp;#32;TLS&amp;amp;#32;или&amp;amp;#32;587&amp;amp;#32;STARTTLS&amp;amp;#32;+&amp;amp;#32;AUTH&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;LMTP&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;/opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;AUTH&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;файловый&amp;amp;#32;каталог&amp;amp;#32;пользователей&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;993&amp;amp;#32;TLS&amp;amp;#32;→&amp;amp;#32;Dovecot&lt;br /&gt;
SnappyMail&amp;amp;#32;(необязательно)&amp;amp;#32;→&amp;amp;#32;те&amp;amp;#32;же&amp;amp;#32;IMAPS&amp;amp;#32;и&amp;amp;#32;SMTP&amp;amp;#32;AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
sysrc hostname=mail.example.org&lt;br /&gt;
hostname mail.example.org&lt;br /&gt;
sysrc defaultrouter=198.51.100.1&lt;br /&gt;
# Только если в работающей таблице отсутствует default route:&lt;br /&gt;
route add default 198.51.100.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo /dev/da1 | awk &#039;{print $3}&#039;)&lt;br /&gt;
test -n &amp;quot;$disk_bytes&amp;quot; &amp;amp;&amp;amp; test &amp;quot;$disk_bytes&amp;quot; -gt 0 || exit 1&lt;br /&gt;
cmp -s -n &amp;quot;$disk_bytes&amp;quot; /dev/da1 /dev/zero&lt;br /&gt;
printf &#039;cmp status: %s\n&#039; &amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
pkg update&lt;br /&gt;
pkg install -n exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
# После проверки плана:&lt;br /&gt;
pkg install -y exim dovecot24 swaks ca_root_nss acme.sh&lt;br /&gt;
sysrc exim_enable=NO dovecot_enable=NO&lt;br /&gt;
sysrc sendmail_enable=NONE sendmail_submit_enable=NO&lt;br /&gt;
sysrc sendmail_outbound_enable=NO sendmail_msp_queue_enable=NO&lt;br /&gt;
service sendmail onestop&lt;br /&gt;
/usr/local/sbin/exim -bV&lt;br /&gt;
/usr/local/sbin/dovecot --version&lt;br /&gt;
pw usershow mailnull&lt;br /&gt;
pw groupshow mail&lt;br /&gt;
pw groupshow dovecot&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;review&amp;amp;#32;and&amp;amp;#32;explicit&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback.&lt;br /&gt;
&lt;br /&gt;
No&amp;amp;#32;command&amp;amp;#32;trains&amp;amp;#32;automatically.&amp;amp;#32;list/show&amp;amp;#32;are&amp;amp;#32;local&amp;amp;#32;reads.&amp;amp;#32;learn&amp;amp;#32;ID&amp;amp;#32;spam|ham&lt;br /&gt;
is&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;command&amp;amp;#32;that&amp;amp;#32;invokes&amp;amp;#32;rspamc.&amp;amp;#32;reject&amp;amp;#32;ID&amp;amp;#32;archives&amp;amp;#32;without&amp;amp;#32;training.&lt;br /&gt;
Queue&amp;amp;#32;root&amp;amp;#32;and&amp;amp;#32;processed&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;non-writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users;&lt;br /&gt;
pending&amp;amp;#32;is&amp;amp;#32;separately&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;vmail,&amp;amp;#32;mode&amp;amp;#32;0700.&amp;amp;#32;Archived&amp;amp;#32;originals&amp;amp;#32;remain&amp;amp;#32;in&lt;br /&gt;
processed/ID/source;&amp;amp;#32;only&amp;amp;#32;root-owned&amp;amp;#32;snapshots&amp;amp;#32;are&amp;amp;#32;used&amp;amp;#32;for&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;review.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;email&amp;amp;#32;import&amp;amp;#32;policy&lt;br /&gt;
from&amp;amp;#32;email.parser&amp;amp;#32;import&amp;amp;#32;BytesHeaderParser&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
&lt;br /&gt;
spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_collector&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback.py&amp;amp;quot;))&lt;br /&gt;
collector&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;collector.FeedbackError&lt;br /&gt;
ID_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{32}\Z&amp;amp;quot;)&lt;br /&gt;
DEFAULT_ROOT&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback&amp;amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/usr/local/etc/rspamd/controller.password&amp;amp;quot;&lt;br /&gt;
HEADERS&amp;amp;#32;=&amp;amp;#32;(&amp;amp;quot;From&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;To&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Date&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Subject&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Message-ID&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Status&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Score&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Rspamd-Action&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;emit(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;require_root():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;hasattr(os,&amp;amp;#32;&amp;amp;quot;geteuid&amp;amp;quot;)&amp;amp;#32;or&amp;amp;#32;os.geteuid()&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;administrative&amp;amp;#32;command&amp;amp;#32;requires&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_id(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;ID&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;exactly&amp;amp;#32;32&amp;amp;#32;lowercase&amp;amp;#32;hexadecimal&amp;amp;#32;characters&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;Directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Nofollow&amp;amp;#32;directory&amp;amp;#32;descriptor&amp;amp;#32;on&amp;amp;#32;FreeBSD;&amp;amp;#32;portable&amp;amp;#32;fallback&amp;amp;#32;for&amp;amp;#32;local&amp;amp;#32;tests.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__init__(self,&amp;amp;#32;path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;self.path.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;expected&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;os.open(self.path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__enter__(self):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;self&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__exit__(self,&amp;amp;#32;*args):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;read(self,&amp;amp;#32;name,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;&amp;amp;quot;\\&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;.&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;..&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;internal&amp;amp;#32;filename&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(name,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW&amp;amp;#32;|&amp;amp;#32;os.O_NONBLOCK,&amp;amp;#32;dir_fd=self.fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;self.path&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;symlink&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;event&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;rb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;source:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.fstat(source.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;info.st_nlink&amp;amp;#32;!=&amp;amp;#32;1&amp;amp;#32;or&amp;amp;#32;info.st_size&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;bounded,&amp;amp;#32;single-link&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;source.read(limit&amp;amp;#32;+&amp;amp;#32;1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(result)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;exceeded&amp;amp;#32;the&amp;amp;#32;size&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;read_event(directory,&amp;amp;#32;event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;On&amp;amp;#32;POSIX&amp;amp;#32;open&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;relative&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;already&amp;amp;#32;opened&amp;amp;#32;parent&amp;amp;#32;descriptor.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;parent&amp;amp;#32;path&amp;amp;#32;is&amp;amp;#32;deployment-owned,&amp;amp;#32;and&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;event&amp;amp;#32;is&amp;amp;#32;rejected.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(Path(directory)&amp;amp;#32;/&amp;amp;#32;event_id)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;entry.read(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;MAX_MESSAGE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;user&amp;amp;quot;),&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;event_id&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;bytes&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;len(message)&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;hashlib.sha256(message).hexdigest():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;content&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;its&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata,&amp;amp;#32;message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write_private(path,&amp;amp;#32;value,&amp;amp;#32;*,&amp;amp;#32;json_value=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;flags,&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;json_value:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;value&amp;amp;#32;=&amp;amp;#32;(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;\n&amp;amp;quot;).encode()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(value)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;update_receipt(directory,&amp;amp;#32;receipt):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin-new.json&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(temporary,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.replace(temporary,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_root_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;Path(path).stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;administrative&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;group/other&amp;amp;#32;writable&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;receipt_for(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;find_prior(root,&amp;amp;#32;sha256):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;damaged&amp;amp;#32;journal&amp;amp;#32;must&amp;amp;#32;never&amp;amp;#32;silently&amp;amp;#32;allow&amp;amp;#32;repeated&amp;amp;#32;training.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;is&amp;amp;#32;incomplete;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;before&amp;amp;#32;more&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;==&amp;amp;#32;sha256&amp;amp;#32;and&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label=None,&amp;amp;#32;reason=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(root).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;collector.MIN_FREE_BYTES&amp;amp;#32;+&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;#32;to&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;reviewed&amp;amp;#32;snapshot&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;],&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;reviewed_utc&amp;amp;quot;:&amp;amp;#32;collector.utc_now(),&amp;amp;#32;&amp;amp;quot;status&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;preparing&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;selected_class&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;reason&amp;amp;quot;:&amp;amp;#32;reason}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;json.dumps(metadata,&amp;amp;#32;ensure_ascii=True).encode())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;complete&amp;amp;#32;original&amp;amp;#32;event.&amp;amp;#32;Never&amp;amp;#32;traverse&amp;amp;#32;or&amp;amp;#32;execute&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;source/.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id,&amp;amp;#32;archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;status&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;archive,&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;executable.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;path&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[str(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;rspamc&amp;amp;#32;reads&amp;amp;#32;the&amp;amp;#32;file&amp;amp;#32;itself;&amp;amp;#32;the&amp;amp;#32;secret&amp;amp;#32;never&amp;amp;#32;appears&amp;amp;#32;in&amp;amp;#32;argv&amp;amp;#32;or&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;learn_&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;-u/-d:&amp;amp;#32;one&amp;amp;#32;shared&amp;amp;#32;Bayes&amp;amp;#32;classifier.&amp;amp;#32;No&amp;amp;#32;shell&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;input=message,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;timeout=timeout,&amp;amp;#32;check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;learn_event(root,&amp;amp;#32;event_id,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;training&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;prior&amp;amp;#32;=&amp;amp;#32;find_prior(root,&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prior:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;prior&amp;amp;#32;learned&amp;amp;#32;or&amp;amp;#32;uncertain&amp;amp;#32;event;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;and&amp;amp;#32;reject&amp;amp;#32;the&amp;amp;#32;duplicate&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rc&amp;amp;#32;=&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;subprocess.TimeoutExpired:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;timeout;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;automatically&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_failed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;could&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;or&amp;amp;#32;local&amp;amp;#32;configuration&amp;amp;#32;was&amp;amp;#32;invalid&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;rc&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;rc&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;rc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;inspect&amp;amp;#32;server&amp;amp;#32;before&amp;amp;#32;any&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;reject_event(root,&amp;amp;#32;event_id,&amp;amp;#32;reason=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(reason)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;500:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;reason&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;rejected&amp;amp;quot;,&amp;amp;#32;reason=reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;list_events(root,&amp;amp;#32;state,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;((&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;(state,)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;directory.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;directory.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;notice&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;limit&amp;amp;#32;reached&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;limit&amp;amp;quot;:&amp;amp;#32;limit})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(entry)&amp;amp;#32;as&amp;amp;#32;item:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(item.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;metadata.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;id&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;state&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;review&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(row)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;state&amp;amp;quot;:&amp;amp;#32;name,&amp;amp;#32;&amp;amp;quot;error&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;#32;event;&amp;amp;#32;manual&amp;amp;#32;inspection&amp;amp;#32;required&amp;amp;quot;})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;show_event(root,&amp;amp;#32;event_id,&amp;amp;#32;state,&amp;amp;#32;path_only):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;state,&amp;amp;#32;check_id(event_id))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path_only:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;message_path&amp;amp;quot;:&amp;amp;#32;str(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;)})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;headers&amp;amp;#32;=&amp;amp;#32;BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message),&amp;amp;#32;262144)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;metadata&amp;amp;quot;:&amp;amp;#32;metadata,&amp;amp;#32;&amp;amp;quot;headers&amp;amp;quot;:&amp;amp;#32;{name:&amp;amp;#32;[str(v)[:2000]&amp;amp;#32;for&amp;amp;#32;v&amp;amp;#32;in&amp;amp;#32;headers.get_all(name,&amp;amp;#32;[])[:10]]&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;HEADERS}})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;review&amp;amp;quot;:&amp;amp;#32;receipt_for(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;warn_processed_storage(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;event&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event.name)&amp;amp;#32;or&amp;amp;#32;event.is_symlink()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;event.is_dir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;prefix&amp;amp;#32;in&amp;amp;#32;(event,&amp;amp;#32;event&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prefix.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(prefix&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_BYTES&amp;amp;#32;or&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;WARNING&amp;amp;#32;processed&amp;amp;#32;archive&amp;amp;#32;reached&amp;amp;#32;512&amp;amp;#32;MiB&amp;amp;#32;or&amp;amp;#32;1024&amp;amp;#32;events;&amp;amp;#32;review&amp;amp;#32;retention&amp;amp;#32;manually;&amp;amp;#32;nothing&amp;amp;#32;deleted&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sub&amp;amp;#32;=&amp;amp;#32;parser.add_subparsers(dest=&amp;amp;quot;command&amp;amp;quot;,&amp;amp;#32;required=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;list&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;JSON&amp;amp;#32;lines,&amp;amp;#32;metadata&amp;amp;#32;only&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--limit&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=100)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;show&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;selected&amp;amp;#32;headers&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;dumps&amp;amp;#32;the&amp;amp;#32;body&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--path&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;learn&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;explicitly&amp;amp;#32;train&amp;amp;#32;one&amp;amp;#32;reviewed&amp;amp;#32;event,&amp;amp;#32;then&amp;amp;#32;archive&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;help=&amp;amp;quot;root-only&amp;amp;#32;plaintext&amp;amp;#32;secret&amp;amp;#32;file;&amp;amp;#32;use&amp;amp;#32;--no-password&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;an&amp;amp;#32;intentionally&amp;amp;#32;trusted&amp;amp;#32;local&amp;amp;#32;controller&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--no-password&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;reject&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;archive&amp;amp;#32;one&amp;amp;#32;event&amp;amp;#32;without&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;deleting&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;--reason&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;root&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn_processed_storage(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;list&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.limit&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;10000:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;limit&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;1&amp;amp;#32;and&amp;amp;#32;10000&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;list_events(root,&amp;amp;#32;args.state,&amp;amp;#32;args.limit)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;show&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;show_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.state,&amp;amp;#32;args.path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;reject&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(reject_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.reason))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;timeout&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;0&amp;amp;#32;and&amp;amp;#32;300&amp;amp;#32;seconds&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;learn_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.label,&amp;amp;#32;executable=args.rspamc_bin,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;controller=args.controller,&amp;amp;#32;password_file=None&amp;amp;#32;if&amp;amp;#32;args.no_password&amp;amp;#32;else&amp;amp;#32;args.password_file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timeout=args.timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(result)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&amp;amp;#32;if&amp;amp;#32;result[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;operation&amp;amp;#32;refused&amp;amp;#32;or&amp;amp;#32;failed;&amp;amp;#32;inspect&amp;amp;#32;configuration/event&amp;amp;#32;locally&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;background&amp;amp;#32;learning&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;bounded&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback&amp;amp;#32;queue.&lt;br /&gt;
&lt;br /&gt;
The&amp;amp;#32;collector&amp;amp;#32;remains&amp;amp;#32;a&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;writer.&amp;amp;#32;This&amp;amp;#32;process&amp;amp;#32;owns&amp;amp;#32;controller&lt;br /&gt;
credentials,&amp;amp;#32;learns&amp;amp;#32;only&amp;amp;#32;validated&amp;amp;#32;INBOX/Junk&amp;amp;#32;events,&amp;amp;#32;preserves&amp;amp;#32;every&amp;amp;#32;original,&lt;br /&gt;
and&amp;amp;#32;never&amp;amp;#32;retries&amp;amp;#32;a&amp;amp;#32;training&amp;amp;#32;operation&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result.&amp;amp;#32;Run&amp;amp;#32;foreground&lt;br /&gt;
under&amp;amp;#32;FreeBSD&amp;amp;#32;daemon(8),&amp;amp;#32;or&amp;amp;#32;use&amp;amp;#32;--once&amp;amp;#32;for&amp;amp;#32;one&amp;amp;#32;bounded&amp;amp;#32;batch.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;signal&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;threading&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
&lt;br /&gt;
_spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_worker_admin&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback-admin.py&amp;amp;quot;))&lt;br /&gt;
admin&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;admin.FeedbackError&lt;br /&gt;
SHA_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{64}\Z&amp;amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
UNCERTAIN&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;log_event(kind,&amp;amp;#32;**fields):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Callers&amp;amp;#32;pass&amp;amp;#32;only&amp;amp;#32;fixed&amp;amp;#32;diagnostics,&amp;amp;#32;numeric&amp;amp;#32;counts,&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;classes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps({&amp;amp;quot;event&amp;amp;quot;:&amp;amp;#32;kind,&amp;amp;#32;**fields},&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True),&amp;amp;#32;flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_timestamp_ns(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(value,&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;missing&amp;amp;#32;timestamp&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parsed&amp;amp;#32;=&amp;amp;#32;datetime.fromisoformat(value.replace(&amp;amp;quot;Z&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;+00:00&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;parsed.tzinfo&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;timestamp&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;timezone&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;int(parsed.timestamp()&amp;amp;#32;*&amp;amp;#32;1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;order_events(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;bounded&amp;amp;#32;metadata,&amp;amp;#32;ordered&amp;amp;#32;by&amp;amp;#32;collector&amp;amp;#32;created_ns,&amp;amp;#32;then&amp;amp;#32;event&amp;amp;#32;ID.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;Legacy&amp;amp;#32;second-resolution&amp;amp;#32;timestamps&amp;amp;#32;are&amp;amp;#32;permitted&amp;amp;#32;only&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;same&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;contradictory&amp;amp;#32;labels&amp;amp;#32;at&amp;amp;#32;an&amp;amp;#32;identical&amp;amp;#32;timestamp.&amp;amp;#32;Such&amp;amp;#32;a&amp;amp;#32;tie&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cannot&amp;amp;#32;be&amp;amp;#32;resolved&amp;amp;#32;from&amp;amp;#32;random&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;is&amp;amp;#32;quarantined&amp;amp;#32;without&amp;amp;#32;learning.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pending&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows&amp;amp;#32;=&amp;amp;#32;[]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(pending)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;queue&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending&amp;amp;#32;/&amp;amp;#32;entry.name)&amp;amp;#32;as&amp;amp;#32;directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(directory.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;dict(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;sha256&amp;amp;quot;),&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(metadata[&amp;amp;quot;sha256&amp;amp;quot;]):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timestamp&amp;amp;#32;=&amp;amp;#32;metadata.get(&amp;amp;quot;created_ns&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;int)&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;bool)&amp;amp;#32;and&amp;amp;#32;timestamp&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;timestamp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;metadata:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(metadata.get(&amp;amp;quot;created_utc&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;manual&amp;amp;#32;administrator&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;archived&amp;amp;#32;this&amp;amp;#32;candidate.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;_invalid&amp;amp;quot;:&amp;amp;#32;True,&amp;amp;#32;&amp;amp;quot;_order_ns&amp;amp;quot;:&amp;amp;#32;0})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied&amp;amp;#32;=&amp;amp;#32;{}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied.setdefault((row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;]),&amp;amp;#32;set()).add(row[&amp;amp;quot;label&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;)&amp;amp;#32;and&amp;amp;#32;len(tied[(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;])])&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;sorted(rows,&amp;amp;#32;key=lambda&amp;amp;#32;row:&amp;amp;#32;(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;id&amp;amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_journal(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;root-owned&amp;amp;#32;receipts;&amp;amp;#32;successful&amp;amp;#32;classes&amp;amp;#32;use&amp;amp;#32;durable&amp;amp;#32;worker&amp;amp;#32;sequence.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked&amp;amp;#32;=&amp;amp;#32;{},&amp;amp;#32;set()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;used_bytes&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(processed)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;MAX_JOURNAL_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;processed&amp;amp;#32;/&amp;amp;#32;entry.name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.receipt_for(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(receipt,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;receipt.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;receipt&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(digest,&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(digest):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;worker_sequence&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(sequence,&amp;amp;#32;int)&amp;amp;#32;or&amp;amp;#32;isinstance(sequence,&amp;amp;#32;bool)&amp;amp;#32;or&amp;amp;#32;sequence&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;sequence&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;max(max_sequence,&amp;amp;#32;sequence)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;in&amp;amp;#32;UNCERTAIN:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;selected_class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;learned&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Existing&amp;amp;#32;manual&amp;amp;#32;receipts&amp;amp;#32;predate&amp;amp;#32;automatic&amp;amp;#32;processing;&amp;amp;#32;the&amp;amp;#32;old&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;admin&amp;amp;#32;refuses&amp;amp;#32;further&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;a&amp;amp;#32;SHA&amp;amp;#32;once&amp;amp;#32;it&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;receipt.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fallback&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(receipt.get(&amp;amp;quot;finished_utc&amp;amp;quot;,&amp;amp;#32;receipt.get(&amp;amp;quot;reviewed_utc&amp;amp;quot;)))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;position&amp;amp;#32;=&amp;amp;#32;(bool(sequence),&amp;amp;#32;sequence&amp;amp;#32;if&amp;amp;#32;sequence&amp;amp;#32;else&amp;amp;#32;fallback,&amp;amp;#32;entry.name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;or&amp;amp;#32;position&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;latest[digest][0]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;(position,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Count&amp;amp;#32;snapshots&amp;amp;#32;and&amp;amp;#32;preserved&amp;amp;#32;originals,&amp;amp;#32;never&amp;amp;#32;recursively&amp;amp;#32;walk&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;arbitrary&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail-user-owned&amp;amp;#32;source/&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;part&amp;amp;#32;in&amp;amp;#32;(directory,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(part)&amp;amp;#32;as&amp;amp;#32;safe:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;safe.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.stat(name,&amp;amp;#32;dir_fd=safe.fd,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(part&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_check_trusted_parent_chain(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;parent&amp;amp;#32;in&amp;amp;#32;Path(path).parents:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;link_info&amp;amp;#32;=&amp;amp;#32;parent.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;parent.stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(link_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;or&amp;amp;#32;info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;executable&amp;amp;#32;parent&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;controlled&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;resolve_executable(executable):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Allow&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;symlinks,&amp;amp;#32;but&amp;amp;#32;pin&amp;amp;#32;both&amp;amp;#32;operations&amp;amp;#32;to&amp;amp;#32;their&amp;amp;#32;target.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;source.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source_info&amp;amp;#32;=&amp;amp;#32;source.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;source_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;entry&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(source)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target&amp;amp;#32;=&amp;amp;#32;source.resolve(strict=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;target.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(target)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;controller_ready(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Authenticated&amp;amp;#32;read-only&amp;amp;#32;controller&amp;amp;#32;probe;&amp;amp;#32;no&amp;amp;#32;candidate&amp;amp;#32;is&amp;amp;#32;archived&amp;amp;#32;yet.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[resolve_executable(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unsafe&amp;amp;#32;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;stat&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;stdin=subprocess.DEVNULL,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;check=False,&amp;amp;#32;timeout=min(timeout,&amp;amp;#32;5)).returncode&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;process_pending(root,&amp;amp;#32;*,&amp;amp;#32;executable=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;,&amp;amp;#32;controller=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=admin.DEFAULT_PASSWORD_FILE,&amp;amp;#32;timeout=30,&amp;amp;#32;batch_size=20,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight=None,&amp;amp;#32;learner=None,&amp;amp;#32;stop_event=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Process&amp;amp;#32;at&amp;amp;#32;most&amp;amp;#32;batch_size&amp;amp;#32;candidates.&amp;amp;#32;Inject&amp;amp;#32;probes/learner&amp;amp;#32;for&amp;amp;#32;unit&amp;amp;#32;tests.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;One&amp;amp;#32;admin&amp;amp;#32;lock&amp;amp;#32;serializes&amp;amp;#32;manual&amp;amp;#32;and&amp;amp;#32;background&amp;amp;#32;operations.&amp;amp;#32;A&amp;amp;#32;crash&amp;amp;#32;after&amp;amp;#32;a&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;durable&amp;amp;#32;learning&amp;amp;#32;intent&amp;amp;#32;leaves&amp;amp;#32;status=learning,&amp;amp;#32;which&amp;amp;#32;blocks&amp;amp;#32;this&amp;amp;#32;SHA&amp;amp;#32;on&amp;amp;#32;the&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;next&amp;amp;#32;pass;&amp;amp;#32;subsequent&amp;amp;#32;candidates&amp;amp;#32;are&amp;amp;#32;preserved&amp;amp;#32;as&amp;amp;#32;quarantined_uncertain.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight&amp;amp;#32;=&amp;amp;#32;controller_ready&amp;amp;#32;if&amp;amp;#32;preflight&amp;amp;#32;is&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;preflight&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;learner&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;pkg&amp;amp;#32;installs&amp;amp;#32;rspamc&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;root-owned&amp;amp;#32;versioned&amp;amp;#32;symlink.&amp;amp;#32;Resolve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;it&amp;amp;#32;once&amp;amp;#32;so&amp;amp;#32;preflight&amp;amp;#32;and&amp;amp;#32;training&amp;amp;#32;invoke&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;trusted&amp;amp;#32;executable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;resolve_executable(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learner&amp;amp;#32;=&amp;amp;#32;admin.run_learning&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;learned&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;quot;:&amp;amp;#32;0,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;controller_unavailable&amp;amp;quot;:&amp;amp;#32;False,&amp;amp;#32;&amp;amp;quot;storage_blocked&amp;amp;quot;:&amp;amp;#32;False,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;archive_warning&amp;amp;quot;:&amp;amp;#32;False}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;events&amp;amp;#32;=&amp;amp;#32;order_events(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;len(events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;archive_count&amp;amp;#32;=&amp;amp;#32;_journal(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;preflight(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;candidate&amp;amp;#32;in&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;handled&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;batch_size&amp;amp;#32;or&amp;amp;#32;(stop_event&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;and&amp;amp;#32;stop_event.is_set()):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;candidate[&amp;amp;quot;id&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id).exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Crash&amp;amp;#32;before&amp;amp;#32;prepare_archive&amp;amp;#32;moved&amp;amp;#32;pending/ID:&amp;amp;#32;its&amp;amp;#32;root-owned&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;intent&amp;amp;#32;is&amp;amp;#32;authoritative.&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;pending&amp;amp;#32;original&amp;amp;#32;for&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;manual&amp;amp;#32;recovery&amp;amp;#32;and&amp;amp;#32;continue&amp;amp;#32;independent&amp;amp;#32;content,&amp;amp;#32;never&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;admin.read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;act&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;queued&amp;amp;#32;ordering/identity&amp;amp;#32;changed&amp;amp;#32;since&amp;amp;#32;enumeration.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;any(metadata.get(key)&amp;amp;#32;!=&amp;amp;#32;candidate.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(&amp;amp;quot;sha256&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;+&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label,&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;metadata[&amp;amp;quot;label&amp;amp;quot;],&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_ordering_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;event&amp;amp;#32;ordering&amp;amp;#32;is&amp;amp;#32;ambiguous&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;blocked:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;a&amp;amp;#32;prior&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;#32;has&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;and&amp;amp;#32;latest[digest][1]&amp;amp;#32;==&amp;amp;#32;label:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;same&amp;amp;#32;class&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;latest&amp;amp;#32;successful&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;automatic&amp;amp;#32;explicit&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label,&amp;amp;#32;reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;prepare_archive&amp;amp;#32;writes&amp;amp;#32;a&amp;amp;#32;root&amp;amp;#32;journal&amp;amp;#32;before&amp;amp;#32;moving&amp;amp;#32;the&amp;amp;#32;source;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;stop&amp;amp;#32;instead&amp;amp;#32;of&amp;amp;#32;retrying&amp;amp;#32;an&amp;amp;#32;incompletely&amp;amp;#32;prepared&amp;amp;#32;operation.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt.update({&amp;amp;quot;mode&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;automatic_imap_feedback&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;worker_sequence&amp;amp;quot;:&amp;amp;#32;sequence,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;processed_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&amp;amp;#32;&amp;amp;quot;source_created_ns&amp;amp;quot;:&amp;amp;#32;candidate[&amp;amp;quot;_order_ns&amp;amp;quot;]})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;previous_successful_class&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;latest[digest][1]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Durable&amp;amp;#32;intent&amp;amp;#32;exists&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;call&amp;amp;#32;that&amp;amp;#32;may&amp;amp;#32;change&amp;amp;#32;Bayes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;returncode&amp;amp;#32;=&amp;amp;#32;learner(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;failure;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;returncode&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;admin.collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;((True,&amp;amp;#32;sequence,&amp;amp;#32;event_id),&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;learned&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;duplicates&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive_count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;-=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;feedback_processed&amp;amp;quot;,&amp;amp;#32;id=event_id,&amp;amp;#32;label=label,&amp;amp;#32;status=receipt[&amp;amp;quot;status&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;controller&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;failed&amp;amp;#32;after&amp;amp;#32;the&amp;amp;#32;preflight.&amp;amp;#32;Leave&amp;amp;#32;all&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;other&amp;amp;#32;candidates&amp;amp;#32;pending&amp;amp;#32;until&amp;amp;#32;a&amp;amp;#32;fresh&amp;amp;#32;read-only&amp;amp;#32;probe&amp;amp;#32;passes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--poll&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--batch-size&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=20)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--once&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0.2&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.poll&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;60&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.batch_size&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;100:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;worker&amp;amp;#32;options&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping&amp;amp;#32;=&amp;amp;#32;threading.Event()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;signum&amp;amp;#32;in&amp;amp;#32;(signal.SIGTERM,&amp;amp;#32;signal.SIGINT):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;signal.signal(signum,&amp;amp;#32;lambda&amp;amp;#32;*_:&amp;amp;#32;stopping.set())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;lockfd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.worker.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(lockfd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;not&amp;amp;#32;stopping.is_set():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;process_pending(root,&amp;amp;#32;executable=args.rspamc_bin,&amp;amp;#32;controller=args.controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=args.password_file,&amp;amp;#32;timeout=args.timeout,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;batch_size=args.batch_size,&amp;amp;#32;stop_event=stopping)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;state&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;value&amp;amp;#32;for&amp;amp;#32;key,&amp;amp;#32;value&amp;amp;#32;in&amp;amp;#32;summary.items()&amp;amp;#32;if&amp;amp;#32;key&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;!=&amp;amp;#32;previous_state&amp;amp;#32;or&amp;amp;#32;any(summary[key]&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_state&amp;amp;quot;,&amp;amp;#32;**summary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;state&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;2&amp;amp;#32;if&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;or&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;else&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;previous_state&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_error&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;local&amp;amp;#32;queue,&amp;amp;#32;journal&amp;amp;#32;or&amp;amp;#32;configuration&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;#32;of&amp;amp;#32;uncertain&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping.wait(args.poll)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_start_failed&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;root,&amp;amp;#32;configuration&amp;amp;#32;or&amp;amp;#32;worker&amp;amp;#32;lock&amp;amp;#32;required&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(1)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=682</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=682"/>
		<updated>2026-10-29T23:27:49Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 3. Однократно создать UFS на пустом da1 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;25&amp;amp;#32;/&amp;amp;#32;STARTTLS&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;465&amp;amp;#32;TLS&amp;amp;#32;или&amp;amp;#32;587&amp;amp;#32;STARTTLS&amp;amp;#32;+&amp;amp;#32;AUTH&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;LMTP&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;/opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;AUTH&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;файловый&amp;amp;#32;каталог&amp;amp;#32;пользователей&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;993&amp;amp;#32;TLS&amp;amp;#32;→&amp;amp;#32;Dovecot&lt;br /&gt;
SnappyMail&amp;amp;#32;(необязательно)&amp;amp;#32;→&amp;amp;#32;те&amp;amp;#32;же&amp;amp;#32;IMAPS&amp;amp;#32;и&amp;amp;#32;SMTP&amp;amp;#32;AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
sysrc hostname=mail.example.org&lt;br /&gt;
hostname mail.example.org&lt;br /&gt;
sysrc defaultrouter=198.51.100.1&lt;br /&gt;
# Только если в работающей таблице отсутствует default route:&lt;br /&gt;
route add default 198.51.100.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo /dev/da1 | awk &#039;{print $3}&#039;)&lt;br /&gt;
test -n &amp;quot;$disk_bytes&amp;quot; &amp;amp;&amp;amp; test &amp;quot;$disk_bytes&amp;quot; -gt 0 || exit 1&lt;br /&gt;
cmp -s -n &amp;quot;$disk_bytes&amp;quot; /dev/da1 /dev/zero&lt;br /&gt;
printf &#039;cmp status: %s\n&#039; &amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;update&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
#&amp;amp;#32;После&amp;amp;#32;проверки&amp;amp;#32;плана:&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
sysrc&amp;amp;#32;exim_enable=NO&amp;amp;#32;dovecot_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_enable=NONE&amp;amp;#32;sendmail_submit_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_outbound_enable=NO&amp;amp;#32;sendmail_msp_queue_enable=NO&lt;br /&gt;
service&amp;amp;#32;sendmail&amp;amp;#32;onestop&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/dovecot&amp;amp;#32;--version&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;mailnull&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;mail&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;review&amp;amp;#32;and&amp;amp;#32;explicit&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback.&lt;br /&gt;
&lt;br /&gt;
No&amp;amp;#32;command&amp;amp;#32;trains&amp;amp;#32;automatically.&amp;amp;#32;list/show&amp;amp;#32;are&amp;amp;#32;local&amp;amp;#32;reads.&amp;amp;#32;learn&amp;amp;#32;ID&amp;amp;#32;spam|ham&lt;br /&gt;
is&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;command&amp;amp;#32;that&amp;amp;#32;invokes&amp;amp;#32;rspamc.&amp;amp;#32;reject&amp;amp;#32;ID&amp;amp;#32;archives&amp;amp;#32;without&amp;amp;#32;training.&lt;br /&gt;
Queue&amp;amp;#32;root&amp;amp;#32;and&amp;amp;#32;processed&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;non-writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users;&lt;br /&gt;
pending&amp;amp;#32;is&amp;amp;#32;separately&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;vmail,&amp;amp;#32;mode&amp;amp;#32;0700.&amp;amp;#32;Archived&amp;amp;#32;originals&amp;amp;#32;remain&amp;amp;#32;in&lt;br /&gt;
processed/ID/source;&amp;amp;#32;only&amp;amp;#32;root-owned&amp;amp;#32;snapshots&amp;amp;#32;are&amp;amp;#32;used&amp;amp;#32;for&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;review.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;email&amp;amp;#32;import&amp;amp;#32;policy&lt;br /&gt;
from&amp;amp;#32;email.parser&amp;amp;#32;import&amp;amp;#32;BytesHeaderParser&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
&lt;br /&gt;
spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_collector&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback.py&amp;amp;quot;))&lt;br /&gt;
collector&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;collector.FeedbackError&lt;br /&gt;
ID_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{32}\Z&amp;amp;quot;)&lt;br /&gt;
DEFAULT_ROOT&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback&amp;amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/usr/local/etc/rspamd/controller.password&amp;amp;quot;&lt;br /&gt;
HEADERS&amp;amp;#32;=&amp;amp;#32;(&amp;amp;quot;From&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;To&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Date&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Subject&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Message-ID&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Status&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Score&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Rspamd-Action&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;emit(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;require_root():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;hasattr(os,&amp;amp;#32;&amp;amp;quot;geteuid&amp;amp;quot;)&amp;amp;#32;or&amp;amp;#32;os.geteuid()&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;administrative&amp;amp;#32;command&amp;amp;#32;requires&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_id(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;ID&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;exactly&amp;amp;#32;32&amp;amp;#32;lowercase&amp;amp;#32;hexadecimal&amp;amp;#32;characters&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;Directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Nofollow&amp;amp;#32;directory&amp;amp;#32;descriptor&amp;amp;#32;on&amp;amp;#32;FreeBSD;&amp;amp;#32;portable&amp;amp;#32;fallback&amp;amp;#32;for&amp;amp;#32;local&amp;amp;#32;tests.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__init__(self,&amp;amp;#32;path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;self.path.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;expected&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;os.open(self.path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__enter__(self):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;self&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__exit__(self,&amp;amp;#32;*args):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;read(self,&amp;amp;#32;name,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;&amp;amp;quot;\\&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;.&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;..&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;internal&amp;amp;#32;filename&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(name,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW&amp;amp;#32;|&amp;amp;#32;os.O_NONBLOCK,&amp;amp;#32;dir_fd=self.fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;self.path&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;symlink&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;event&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;rb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;source:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.fstat(source.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;info.st_nlink&amp;amp;#32;!=&amp;amp;#32;1&amp;amp;#32;or&amp;amp;#32;info.st_size&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;bounded,&amp;amp;#32;single-link&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;source.read(limit&amp;amp;#32;+&amp;amp;#32;1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(result)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;exceeded&amp;amp;#32;the&amp;amp;#32;size&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;read_event(directory,&amp;amp;#32;event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;On&amp;amp;#32;POSIX&amp;amp;#32;open&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;relative&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;already&amp;amp;#32;opened&amp;amp;#32;parent&amp;amp;#32;descriptor.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;parent&amp;amp;#32;path&amp;amp;#32;is&amp;amp;#32;deployment-owned,&amp;amp;#32;and&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;event&amp;amp;#32;is&amp;amp;#32;rejected.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(Path(directory)&amp;amp;#32;/&amp;amp;#32;event_id)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;entry.read(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;MAX_MESSAGE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;user&amp;amp;quot;),&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;event_id&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;bytes&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;len(message)&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;hashlib.sha256(message).hexdigest():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;content&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;its&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata,&amp;amp;#32;message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write_private(path,&amp;amp;#32;value,&amp;amp;#32;*,&amp;amp;#32;json_value=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;flags,&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;json_value:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;value&amp;amp;#32;=&amp;amp;#32;(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;\n&amp;amp;quot;).encode()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(value)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;update_receipt(directory,&amp;amp;#32;receipt):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin-new.json&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(temporary,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.replace(temporary,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_root_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;Path(path).stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;administrative&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;group/other&amp;amp;#32;writable&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;receipt_for(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;find_prior(root,&amp;amp;#32;sha256):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;damaged&amp;amp;#32;journal&amp;amp;#32;must&amp;amp;#32;never&amp;amp;#32;silently&amp;amp;#32;allow&amp;amp;#32;repeated&amp;amp;#32;training.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;is&amp;amp;#32;incomplete;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;before&amp;amp;#32;more&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;==&amp;amp;#32;sha256&amp;amp;#32;and&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label=None,&amp;amp;#32;reason=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(root).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;collector.MIN_FREE_BYTES&amp;amp;#32;+&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;#32;to&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;reviewed&amp;amp;#32;snapshot&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;],&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;reviewed_utc&amp;amp;quot;:&amp;amp;#32;collector.utc_now(),&amp;amp;#32;&amp;amp;quot;status&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;preparing&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;selected_class&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;reason&amp;amp;quot;:&amp;amp;#32;reason}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;json.dumps(metadata,&amp;amp;#32;ensure_ascii=True).encode())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;complete&amp;amp;#32;original&amp;amp;#32;event.&amp;amp;#32;Never&amp;amp;#32;traverse&amp;amp;#32;or&amp;amp;#32;execute&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;source/.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id,&amp;amp;#32;archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;status&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;archive,&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;executable.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;path&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[str(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;rspamc&amp;amp;#32;reads&amp;amp;#32;the&amp;amp;#32;file&amp;amp;#32;itself;&amp;amp;#32;the&amp;amp;#32;secret&amp;amp;#32;never&amp;amp;#32;appears&amp;amp;#32;in&amp;amp;#32;argv&amp;amp;#32;or&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;learn_&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;-u/-d:&amp;amp;#32;one&amp;amp;#32;shared&amp;amp;#32;Bayes&amp;amp;#32;classifier.&amp;amp;#32;No&amp;amp;#32;shell&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;input=message,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;timeout=timeout,&amp;amp;#32;check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;learn_event(root,&amp;amp;#32;event_id,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;training&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;prior&amp;amp;#32;=&amp;amp;#32;find_prior(root,&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prior:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;prior&amp;amp;#32;learned&amp;amp;#32;or&amp;amp;#32;uncertain&amp;amp;#32;event;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;and&amp;amp;#32;reject&amp;amp;#32;the&amp;amp;#32;duplicate&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rc&amp;amp;#32;=&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;subprocess.TimeoutExpired:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;timeout;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;automatically&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_failed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;could&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;or&amp;amp;#32;local&amp;amp;#32;configuration&amp;amp;#32;was&amp;amp;#32;invalid&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;rc&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;rc&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;rc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;inspect&amp;amp;#32;server&amp;amp;#32;before&amp;amp;#32;any&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;reject_event(root,&amp;amp;#32;event_id,&amp;amp;#32;reason=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(reason)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;500:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;reason&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;rejected&amp;amp;quot;,&amp;amp;#32;reason=reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;list_events(root,&amp;amp;#32;state,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;((&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;(state,)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;directory.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;directory.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;notice&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;limit&amp;amp;#32;reached&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;limit&amp;amp;quot;:&amp;amp;#32;limit})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(entry)&amp;amp;#32;as&amp;amp;#32;item:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(item.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;metadata.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;id&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;state&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;review&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(row)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;state&amp;amp;quot;:&amp;amp;#32;name,&amp;amp;#32;&amp;amp;quot;error&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;#32;event;&amp;amp;#32;manual&amp;amp;#32;inspection&amp;amp;#32;required&amp;amp;quot;})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;show_event(root,&amp;amp;#32;event_id,&amp;amp;#32;state,&amp;amp;#32;path_only):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;state,&amp;amp;#32;check_id(event_id))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path_only:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;message_path&amp;amp;quot;:&amp;amp;#32;str(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;)})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;headers&amp;amp;#32;=&amp;amp;#32;BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message),&amp;amp;#32;262144)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;metadata&amp;amp;quot;:&amp;amp;#32;metadata,&amp;amp;#32;&amp;amp;quot;headers&amp;amp;quot;:&amp;amp;#32;{name:&amp;amp;#32;[str(v)[:2000]&amp;amp;#32;for&amp;amp;#32;v&amp;amp;#32;in&amp;amp;#32;headers.get_all(name,&amp;amp;#32;[])[:10]]&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;HEADERS}})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;review&amp;amp;quot;:&amp;amp;#32;receipt_for(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;warn_processed_storage(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;event&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event.name)&amp;amp;#32;or&amp;amp;#32;event.is_symlink()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;event.is_dir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;prefix&amp;amp;#32;in&amp;amp;#32;(event,&amp;amp;#32;event&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prefix.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(prefix&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_BYTES&amp;amp;#32;or&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;WARNING&amp;amp;#32;processed&amp;amp;#32;archive&amp;amp;#32;reached&amp;amp;#32;512&amp;amp;#32;MiB&amp;amp;#32;or&amp;amp;#32;1024&amp;amp;#32;events;&amp;amp;#32;review&amp;amp;#32;retention&amp;amp;#32;manually;&amp;amp;#32;nothing&amp;amp;#32;deleted&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sub&amp;amp;#32;=&amp;amp;#32;parser.add_subparsers(dest=&amp;amp;quot;command&amp;amp;quot;,&amp;amp;#32;required=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;list&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;JSON&amp;amp;#32;lines,&amp;amp;#32;metadata&amp;amp;#32;only&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--limit&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=100)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;show&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;selected&amp;amp;#32;headers&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;dumps&amp;amp;#32;the&amp;amp;#32;body&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--path&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;learn&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;explicitly&amp;amp;#32;train&amp;amp;#32;one&amp;amp;#32;reviewed&amp;amp;#32;event,&amp;amp;#32;then&amp;amp;#32;archive&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;help=&amp;amp;quot;root-only&amp;amp;#32;plaintext&amp;amp;#32;secret&amp;amp;#32;file;&amp;amp;#32;use&amp;amp;#32;--no-password&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;an&amp;amp;#32;intentionally&amp;amp;#32;trusted&amp;amp;#32;local&amp;amp;#32;controller&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--no-password&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;reject&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;archive&amp;amp;#32;one&amp;amp;#32;event&amp;amp;#32;without&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;deleting&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;--reason&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;root&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn_processed_storage(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;list&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.limit&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;10000:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;limit&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;1&amp;amp;#32;and&amp;amp;#32;10000&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;list_events(root,&amp;amp;#32;args.state,&amp;amp;#32;args.limit)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;show&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;show_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.state,&amp;amp;#32;args.path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;reject&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(reject_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.reason))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;timeout&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;0&amp;amp;#32;and&amp;amp;#32;300&amp;amp;#32;seconds&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;learn_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.label,&amp;amp;#32;executable=args.rspamc_bin,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;controller=args.controller,&amp;amp;#32;password_file=None&amp;amp;#32;if&amp;amp;#32;args.no_password&amp;amp;#32;else&amp;amp;#32;args.password_file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timeout=args.timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(result)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&amp;amp;#32;if&amp;amp;#32;result[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;operation&amp;amp;#32;refused&amp;amp;#32;or&amp;amp;#32;failed;&amp;amp;#32;inspect&amp;amp;#32;configuration/event&amp;amp;#32;locally&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;background&amp;amp;#32;learning&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;bounded&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback&amp;amp;#32;queue.&lt;br /&gt;
&lt;br /&gt;
The&amp;amp;#32;collector&amp;amp;#32;remains&amp;amp;#32;a&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;writer.&amp;amp;#32;This&amp;amp;#32;process&amp;amp;#32;owns&amp;amp;#32;controller&lt;br /&gt;
credentials,&amp;amp;#32;learns&amp;amp;#32;only&amp;amp;#32;validated&amp;amp;#32;INBOX/Junk&amp;amp;#32;events,&amp;amp;#32;preserves&amp;amp;#32;every&amp;amp;#32;original,&lt;br /&gt;
and&amp;amp;#32;never&amp;amp;#32;retries&amp;amp;#32;a&amp;amp;#32;training&amp;amp;#32;operation&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result.&amp;amp;#32;Run&amp;amp;#32;foreground&lt;br /&gt;
under&amp;amp;#32;FreeBSD&amp;amp;#32;daemon(8),&amp;amp;#32;or&amp;amp;#32;use&amp;amp;#32;--once&amp;amp;#32;for&amp;amp;#32;one&amp;amp;#32;bounded&amp;amp;#32;batch.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;signal&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;threading&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
&lt;br /&gt;
_spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_worker_admin&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback-admin.py&amp;amp;quot;))&lt;br /&gt;
admin&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;admin.FeedbackError&lt;br /&gt;
SHA_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{64}\Z&amp;amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
UNCERTAIN&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;log_event(kind,&amp;amp;#32;**fields):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Callers&amp;amp;#32;pass&amp;amp;#32;only&amp;amp;#32;fixed&amp;amp;#32;diagnostics,&amp;amp;#32;numeric&amp;amp;#32;counts,&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;classes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps({&amp;amp;quot;event&amp;amp;quot;:&amp;amp;#32;kind,&amp;amp;#32;**fields},&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True),&amp;amp;#32;flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_timestamp_ns(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(value,&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;missing&amp;amp;#32;timestamp&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parsed&amp;amp;#32;=&amp;amp;#32;datetime.fromisoformat(value.replace(&amp;amp;quot;Z&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;+00:00&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;parsed.tzinfo&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;timestamp&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;timezone&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;int(parsed.timestamp()&amp;amp;#32;*&amp;amp;#32;1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;order_events(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;bounded&amp;amp;#32;metadata,&amp;amp;#32;ordered&amp;amp;#32;by&amp;amp;#32;collector&amp;amp;#32;created_ns,&amp;amp;#32;then&amp;amp;#32;event&amp;amp;#32;ID.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;Legacy&amp;amp;#32;second-resolution&amp;amp;#32;timestamps&amp;amp;#32;are&amp;amp;#32;permitted&amp;amp;#32;only&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;same&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;contradictory&amp;amp;#32;labels&amp;amp;#32;at&amp;amp;#32;an&amp;amp;#32;identical&amp;amp;#32;timestamp.&amp;amp;#32;Such&amp;amp;#32;a&amp;amp;#32;tie&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cannot&amp;amp;#32;be&amp;amp;#32;resolved&amp;amp;#32;from&amp;amp;#32;random&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;is&amp;amp;#32;quarantined&amp;amp;#32;without&amp;amp;#32;learning.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pending&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows&amp;amp;#32;=&amp;amp;#32;[]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(pending)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;queue&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending&amp;amp;#32;/&amp;amp;#32;entry.name)&amp;amp;#32;as&amp;amp;#32;directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(directory.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;dict(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;sha256&amp;amp;quot;),&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(metadata[&amp;amp;quot;sha256&amp;amp;quot;]):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timestamp&amp;amp;#32;=&amp;amp;#32;metadata.get(&amp;amp;quot;created_ns&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;int)&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;bool)&amp;amp;#32;and&amp;amp;#32;timestamp&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;timestamp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;metadata:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(metadata.get(&amp;amp;quot;created_utc&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;manual&amp;amp;#32;administrator&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;archived&amp;amp;#32;this&amp;amp;#32;candidate.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;_invalid&amp;amp;quot;:&amp;amp;#32;True,&amp;amp;#32;&amp;amp;quot;_order_ns&amp;amp;quot;:&amp;amp;#32;0})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied&amp;amp;#32;=&amp;amp;#32;{}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied.setdefault((row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;]),&amp;amp;#32;set()).add(row[&amp;amp;quot;label&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;)&amp;amp;#32;and&amp;amp;#32;len(tied[(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;])])&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;sorted(rows,&amp;amp;#32;key=lambda&amp;amp;#32;row:&amp;amp;#32;(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;id&amp;amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_journal(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;root-owned&amp;amp;#32;receipts;&amp;amp;#32;successful&amp;amp;#32;classes&amp;amp;#32;use&amp;amp;#32;durable&amp;amp;#32;worker&amp;amp;#32;sequence.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked&amp;amp;#32;=&amp;amp;#32;{},&amp;amp;#32;set()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;used_bytes&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(processed)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;MAX_JOURNAL_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;processed&amp;amp;#32;/&amp;amp;#32;entry.name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.receipt_for(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(receipt,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;receipt.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;receipt&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(digest,&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(digest):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;worker_sequence&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(sequence,&amp;amp;#32;int)&amp;amp;#32;or&amp;amp;#32;isinstance(sequence,&amp;amp;#32;bool)&amp;amp;#32;or&amp;amp;#32;sequence&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;sequence&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;max(max_sequence,&amp;amp;#32;sequence)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;in&amp;amp;#32;UNCERTAIN:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;selected_class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;learned&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Existing&amp;amp;#32;manual&amp;amp;#32;receipts&amp;amp;#32;predate&amp;amp;#32;automatic&amp;amp;#32;processing;&amp;amp;#32;the&amp;amp;#32;old&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;admin&amp;amp;#32;refuses&amp;amp;#32;further&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;a&amp;amp;#32;SHA&amp;amp;#32;once&amp;amp;#32;it&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;receipt.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fallback&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(receipt.get(&amp;amp;quot;finished_utc&amp;amp;quot;,&amp;amp;#32;receipt.get(&amp;amp;quot;reviewed_utc&amp;amp;quot;)))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;position&amp;amp;#32;=&amp;amp;#32;(bool(sequence),&amp;amp;#32;sequence&amp;amp;#32;if&amp;amp;#32;sequence&amp;amp;#32;else&amp;amp;#32;fallback,&amp;amp;#32;entry.name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;or&amp;amp;#32;position&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;latest[digest][0]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;(position,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Count&amp;amp;#32;snapshots&amp;amp;#32;and&amp;amp;#32;preserved&amp;amp;#32;originals,&amp;amp;#32;never&amp;amp;#32;recursively&amp;amp;#32;walk&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;arbitrary&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail-user-owned&amp;amp;#32;source/&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;part&amp;amp;#32;in&amp;amp;#32;(directory,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(part)&amp;amp;#32;as&amp;amp;#32;safe:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;safe.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.stat(name,&amp;amp;#32;dir_fd=safe.fd,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(part&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_check_trusted_parent_chain(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;parent&amp;amp;#32;in&amp;amp;#32;Path(path).parents:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;link_info&amp;amp;#32;=&amp;amp;#32;parent.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;parent.stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(link_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;or&amp;amp;#32;info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;executable&amp;amp;#32;parent&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;controlled&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;resolve_executable(executable):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Allow&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;symlinks,&amp;amp;#32;but&amp;amp;#32;pin&amp;amp;#32;both&amp;amp;#32;operations&amp;amp;#32;to&amp;amp;#32;their&amp;amp;#32;target.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;source.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source_info&amp;amp;#32;=&amp;amp;#32;source.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;source_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;entry&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(source)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target&amp;amp;#32;=&amp;amp;#32;source.resolve(strict=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;target.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(target)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;controller_ready(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Authenticated&amp;amp;#32;read-only&amp;amp;#32;controller&amp;amp;#32;probe;&amp;amp;#32;no&amp;amp;#32;candidate&amp;amp;#32;is&amp;amp;#32;archived&amp;amp;#32;yet.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[resolve_executable(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unsafe&amp;amp;#32;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;stat&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;stdin=subprocess.DEVNULL,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;check=False,&amp;amp;#32;timeout=min(timeout,&amp;amp;#32;5)).returncode&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;process_pending(root,&amp;amp;#32;*,&amp;amp;#32;executable=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;,&amp;amp;#32;controller=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=admin.DEFAULT_PASSWORD_FILE,&amp;amp;#32;timeout=30,&amp;amp;#32;batch_size=20,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight=None,&amp;amp;#32;learner=None,&amp;amp;#32;stop_event=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Process&amp;amp;#32;at&amp;amp;#32;most&amp;amp;#32;batch_size&amp;amp;#32;candidates.&amp;amp;#32;Inject&amp;amp;#32;probes/learner&amp;amp;#32;for&amp;amp;#32;unit&amp;amp;#32;tests.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;One&amp;amp;#32;admin&amp;amp;#32;lock&amp;amp;#32;serializes&amp;amp;#32;manual&amp;amp;#32;and&amp;amp;#32;background&amp;amp;#32;operations.&amp;amp;#32;A&amp;amp;#32;crash&amp;amp;#32;after&amp;amp;#32;a&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;durable&amp;amp;#32;learning&amp;amp;#32;intent&amp;amp;#32;leaves&amp;amp;#32;status=learning,&amp;amp;#32;which&amp;amp;#32;blocks&amp;amp;#32;this&amp;amp;#32;SHA&amp;amp;#32;on&amp;amp;#32;the&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;next&amp;amp;#32;pass;&amp;amp;#32;subsequent&amp;amp;#32;candidates&amp;amp;#32;are&amp;amp;#32;preserved&amp;amp;#32;as&amp;amp;#32;quarantined_uncertain.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight&amp;amp;#32;=&amp;amp;#32;controller_ready&amp;amp;#32;if&amp;amp;#32;preflight&amp;amp;#32;is&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;preflight&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;learner&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;pkg&amp;amp;#32;installs&amp;amp;#32;rspamc&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;root-owned&amp;amp;#32;versioned&amp;amp;#32;symlink.&amp;amp;#32;Resolve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;it&amp;amp;#32;once&amp;amp;#32;so&amp;amp;#32;preflight&amp;amp;#32;and&amp;amp;#32;training&amp;amp;#32;invoke&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;trusted&amp;amp;#32;executable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;resolve_executable(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learner&amp;amp;#32;=&amp;amp;#32;admin.run_learning&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;learned&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;quot;:&amp;amp;#32;0,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;controller_unavailable&amp;amp;quot;:&amp;amp;#32;False,&amp;amp;#32;&amp;amp;quot;storage_blocked&amp;amp;quot;:&amp;amp;#32;False,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;archive_warning&amp;amp;quot;:&amp;amp;#32;False}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;events&amp;amp;#32;=&amp;amp;#32;order_events(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;len(events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;archive_count&amp;amp;#32;=&amp;amp;#32;_journal(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;preflight(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;candidate&amp;amp;#32;in&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;handled&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;batch_size&amp;amp;#32;or&amp;amp;#32;(stop_event&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;and&amp;amp;#32;stop_event.is_set()):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;candidate[&amp;amp;quot;id&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id).exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Crash&amp;amp;#32;before&amp;amp;#32;prepare_archive&amp;amp;#32;moved&amp;amp;#32;pending/ID:&amp;amp;#32;its&amp;amp;#32;root-owned&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;intent&amp;amp;#32;is&amp;amp;#32;authoritative.&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;pending&amp;amp;#32;original&amp;amp;#32;for&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;manual&amp;amp;#32;recovery&amp;amp;#32;and&amp;amp;#32;continue&amp;amp;#32;independent&amp;amp;#32;content,&amp;amp;#32;never&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;admin.read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;act&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;queued&amp;amp;#32;ordering/identity&amp;amp;#32;changed&amp;amp;#32;since&amp;amp;#32;enumeration.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;any(metadata.get(key)&amp;amp;#32;!=&amp;amp;#32;candidate.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(&amp;amp;quot;sha256&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;+&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label,&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;metadata[&amp;amp;quot;label&amp;amp;quot;],&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_ordering_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;event&amp;amp;#32;ordering&amp;amp;#32;is&amp;amp;#32;ambiguous&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;blocked:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;a&amp;amp;#32;prior&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;#32;has&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;and&amp;amp;#32;latest[digest][1]&amp;amp;#32;==&amp;amp;#32;label:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;same&amp;amp;#32;class&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;latest&amp;amp;#32;successful&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;automatic&amp;amp;#32;explicit&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label,&amp;amp;#32;reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;prepare_archive&amp;amp;#32;writes&amp;amp;#32;a&amp;amp;#32;root&amp;amp;#32;journal&amp;amp;#32;before&amp;amp;#32;moving&amp;amp;#32;the&amp;amp;#32;source;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;stop&amp;amp;#32;instead&amp;amp;#32;of&amp;amp;#32;retrying&amp;amp;#32;an&amp;amp;#32;incompletely&amp;amp;#32;prepared&amp;amp;#32;operation.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt.update({&amp;amp;quot;mode&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;automatic_imap_feedback&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;worker_sequence&amp;amp;quot;:&amp;amp;#32;sequence,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;processed_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&amp;amp;#32;&amp;amp;quot;source_created_ns&amp;amp;quot;:&amp;amp;#32;candidate[&amp;amp;quot;_order_ns&amp;amp;quot;]})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;previous_successful_class&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;latest[digest][1]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Durable&amp;amp;#32;intent&amp;amp;#32;exists&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;call&amp;amp;#32;that&amp;amp;#32;may&amp;amp;#32;change&amp;amp;#32;Bayes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;returncode&amp;amp;#32;=&amp;amp;#32;learner(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;failure;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;returncode&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;admin.collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;((True,&amp;amp;#32;sequence,&amp;amp;#32;event_id),&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;learned&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;duplicates&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive_count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;-=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;feedback_processed&amp;amp;quot;,&amp;amp;#32;id=event_id,&amp;amp;#32;label=label,&amp;amp;#32;status=receipt[&amp;amp;quot;status&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;controller&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;failed&amp;amp;#32;after&amp;amp;#32;the&amp;amp;#32;preflight.&amp;amp;#32;Leave&amp;amp;#32;all&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;other&amp;amp;#32;candidates&amp;amp;#32;pending&amp;amp;#32;until&amp;amp;#32;a&amp;amp;#32;fresh&amp;amp;#32;read-only&amp;amp;#32;probe&amp;amp;#32;passes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--poll&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--batch-size&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=20)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--once&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0.2&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.poll&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;60&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.batch_size&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;100:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;worker&amp;amp;#32;options&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping&amp;amp;#32;=&amp;amp;#32;threading.Event()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;signum&amp;amp;#32;in&amp;amp;#32;(signal.SIGTERM,&amp;amp;#32;signal.SIGINT):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;signal.signal(signum,&amp;amp;#32;lambda&amp;amp;#32;*_:&amp;amp;#32;stopping.set())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;lockfd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.worker.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(lockfd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;not&amp;amp;#32;stopping.is_set():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;process_pending(root,&amp;amp;#32;executable=args.rspamc_bin,&amp;amp;#32;controller=args.controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=args.password_file,&amp;amp;#32;timeout=args.timeout,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;batch_size=args.batch_size,&amp;amp;#32;stop_event=stopping)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;state&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;value&amp;amp;#32;for&amp;amp;#32;key,&amp;amp;#32;value&amp;amp;#32;in&amp;amp;#32;summary.items()&amp;amp;#32;if&amp;amp;#32;key&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;!=&amp;amp;#32;previous_state&amp;amp;#32;or&amp;amp;#32;any(summary[key]&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_state&amp;amp;quot;,&amp;amp;#32;**summary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;state&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;2&amp;amp;#32;if&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;or&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;else&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;previous_state&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_error&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;local&amp;amp;#32;queue,&amp;amp;#32;journal&amp;amp;#32;or&amp;amp;#32;configuration&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;#32;of&amp;amp;#32;uncertain&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping.wait(args.poll)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_start_failed&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;root,&amp;amp;#32;configuration&amp;amp;#32;or&amp;amp;#32;worker&amp;amp;#32;lock&amp;amp;#32;required&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(1)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=681</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=681"/>
		<updated>2026-10-29T23:25:55Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 2. Проверить систему без изменений */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;25&amp;amp;#32;/&amp;amp;#32;STARTTLS&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;465&amp;amp;#32;TLS&amp;amp;#32;или&amp;amp;#32;587&amp;amp;#32;STARTTLS&amp;amp;#32;+&amp;amp;#32;AUTH&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;LMTP&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;/opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;AUTH&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;файловый&amp;amp;#32;каталог&amp;amp;#32;пользователей&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;993&amp;amp;#32;TLS&amp;amp;#32;→&amp;amp;#32;Dovecot&lt;br /&gt;
SnappyMail&amp;amp;#32;(необязательно)&amp;amp;#32;→&amp;amp;#32;те&amp;amp;#32;же&amp;amp;#32;IMAPS&amp;amp;#32;и&amp;amp;#32;SMTP&amp;amp;#32;AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
sysrc hostname=mail.example.org&lt;br /&gt;
hostname mail.example.org&lt;br /&gt;
sysrc defaultrouter=198.51.100.1&lt;br /&gt;
# Только если в работающей таблице отсутствует default route:&lt;br /&gt;
route add default 198.51.100.1&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo&amp;amp;#32;/dev/da1&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;{print&amp;amp;#32;$3}&#039;)&lt;br /&gt;
test&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;-gt&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;/dev/da1&amp;amp;#32;/dev/zero&lt;br /&gt;
printf&amp;amp;#32;&#039;cmp&amp;amp;#32;status:&amp;amp;#32;%s\n&#039;&amp;amp;#32;&amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;update&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
#&amp;amp;#32;После&amp;amp;#32;проверки&amp;amp;#32;плана:&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
sysrc&amp;amp;#32;exim_enable=NO&amp;amp;#32;dovecot_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_enable=NONE&amp;amp;#32;sendmail_submit_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_outbound_enable=NO&amp;amp;#32;sendmail_msp_queue_enable=NO&lt;br /&gt;
service&amp;amp;#32;sendmail&amp;amp;#32;onestop&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/dovecot&amp;amp;#32;--version&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;mailnull&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;mail&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;review&amp;amp;#32;and&amp;amp;#32;explicit&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback.&lt;br /&gt;
&lt;br /&gt;
No&amp;amp;#32;command&amp;amp;#32;trains&amp;amp;#32;automatically.&amp;amp;#32;list/show&amp;amp;#32;are&amp;amp;#32;local&amp;amp;#32;reads.&amp;amp;#32;learn&amp;amp;#32;ID&amp;amp;#32;spam|ham&lt;br /&gt;
is&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;command&amp;amp;#32;that&amp;amp;#32;invokes&amp;amp;#32;rspamc.&amp;amp;#32;reject&amp;amp;#32;ID&amp;amp;#32;archives&amp;amp;#32;without&amp;amp;#32;training.&lt;br /&gt;
Queue&amp;amp;#32;root&amp;amp;#32;and&amp;amp;#32;processed&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;non-writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users;&lt;br /&gt;
pending&amp;amp;#32;is&amp;amp;#32;separately&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;vmail,&amp;amp;#32;mode&amp;amp;#32;0700.&amp;amp;#32;Archived&amp;amp;#32;originals&amp;amp;#32;remain&amp;amp;#32;in&lt;br /&gt;
processed/ID/source;&amp;amp;#32;only&amp;amp;#32;root-owned&amp;amp;#32;snapshots&amp;amp;#32;are&amp;amp;#32;used&amp;amp;#32;for&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;review.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;email&amp;amp;#32;import&amp;amp;#32;policy&lt;br /&gt;
from&amp;amp;#32;email.parser&amp;amp;#32;import&amp;amp;#32;BytesHeaderParser&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
&lt;br /&gt;
spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_collector&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback.py&amp;amp;quot;))&lt;br /&gt;
collector&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;collector.FeedbackError&lt;br /&gt;
ID_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{32}\Z&amp;amp;quot;)&lt;br /&gt;
DEFAULT_ROOT&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback&amp;amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/usr/local/etc/rspamd/controller.password&amp;amp;quot;&lt;br /&gt;
HEADERS&amp;amp;#32;=&amp;amp;#32;(&amp;amp;quot;From&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;To&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Date&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Subject&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Message-ID&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Status&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Score&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Rspamd-Action&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;emit(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;require_root():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;hasattr(os,&amp;amp;#32;&amp;amp;quot;geteuid&amp;amp;quot;)&amp;amp;#32;or&amp;amp;#32;os.geteuid()&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;administrative&amp;amp;#32;command&amp;amp;#32;requires&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_id(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;ID&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;exactly&amp;amp;#32;32&amp;amp;#32;lowercase&amp;amp;#32;hexadecimal&amp;amp;#32;characters&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;Directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Nofollow&amp;amp;#32;directory&amp;amp;#32;descriptor&amp;amp;#32;on&amp;amp;#32;FreeBSD;&amp;amp;#32;portable&amp;amp;#32;fallback&amp;amp;#32;for&amp;amp;#32;local&amp;amp;#32;tests.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__init__(self,&amp;amp;#32;path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;self.path.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;expected&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;os.open(self.path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__enter__(self):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;self&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__exit__(self,&amp;amp;#32;*args):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;read(self,&amp;amp;#32;name,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;&amp;amp;quot;\\&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;.&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;..&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;internal&amp;amp;#32;filename&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(name,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW&amp;amp;#32;|&amp;amp;#32;os.O_NONBLOCK,&amp;amp;#32;dir_fd=self.fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;self.path&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;symlink&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;event&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;rb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;source:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.fstat(source.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;info.st_nlink&amp;amp;#32;!=&amp;amp;#32;1&amp;amp;#32;or&amp;amp;#32;info.st_size&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;bounded,&amp;amp;#32;single-link&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;source.read(limit&amp;amp;#32;+&amp;amp;#32;1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(result)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;exceeded&amp;amp;#32;the&amp;amp;#32;size&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;read_event(directory,&amp;amp;#32;event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;On&amp;amp;#32;POSIX&amp;amp;#32;open&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;relative&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;already&amp;amp;#32;opened&amp;amp;#32;parent&amp;amp;#32;descriptor.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;parent&amp;amp;#32;path&amp;amp;#32;is&amp;amp;#32;deployment-owned,&amp;amp;#32;and&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;event&amp;amp;#32;is&amp;amp;#32;rejected.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(Path(directory)&amp;amp;#32;/&amp;amp;#32;event_id)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;entry.read(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;MAX_MESSAGE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;user&amp;amp;quot;),&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;event_id&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;bytes&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;len(message)&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;hashlib.sha256(message).hexdigest():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;content&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;its&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata,&amp;amp;#32;message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write_private(path,&amp;amp;#32;value,&amp;amp;#32;*,&amp;amp;#32;json_value=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;flags,&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;json_value:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;value&amp;amp;#32;=&amp;amp;#32;(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;\n&amp;amp;quot;).encode()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(value)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;update_receipt(directory,&amp;amp;#32;receipt):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin-new.json&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(temporary,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.replace(temporary,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_root_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;Path(path).stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;administrative&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;group/other&amp;amp;#32;writable&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;receipt_for(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;find_prior(root,&amp;amp;#32;sha256):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;damaged&amp;amp;#32;journal&amp;amp;#32;must&amp;amp;#32;never&amp;amp;#32;silently&amp;amp;#32;allow&amp;amp;#32;repeated&amp;amp;#32;training.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;is&amp;amp;#32;incomplete;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;before&amp;amp;#32;more&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;==&amp;amp;#32;sha256&amp;amp;#32;and&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label=None,&amp;amp;#32;reason=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(root).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;collector.MIN_FREE_BYTES&amp;amp;#32;+&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;#32;to&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;reviewed&amp;amp;#32;snapshot&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;],&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;reviewed_utc&amp;amp;quot;:&amp;amp;#32;collector.utc_now(),&amp;amp;#32;&amp;amp;quot;status&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;preparing&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;selected_class&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;reason&amp;amp;quot;:&amp;amp;#32;reason}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;json.dumps(metadata,&amp;amp;#32;ensure_ascii=True).encode())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;complete&amp;amp;#32;original&amp;amp;#32;event.&amp;amp;#32;Never&amp;amp;#32;traverse&amp;amp;#32;or&amp;amp;#32;execute&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;source/.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id,&amp;amp;#32;archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;status&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;archive,&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;executable.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;path&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[str(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;rspamc&amp;amp;#32;reads&amp;amp;#32;the&amp;amp;#32;file&amp;amp;#32;itself;&amp;amp;#32;the&amp;amp;#32;secret&amp;amp;#32;never&amp;amp;#32;appears&amp;amp;#32;in&amp;amp;#32;argv&amp;amp;#32;or&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;learn_&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;-u/-d:&amp;amp;#32;one&amp;amp;#32;shared&amp;amp;#32;Bayes&amp;amp;#32;classifier.&amp;amp;#32;No&amp;amp;#32;shell&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;input=message,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;timeout=timeout,&amp;amp;#32;check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;learn_event(root,&amp;amp;#32;event_id,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;training&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;prior&amp;amp;#32;=&amp;amp;#32;find_prior(root,&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prior:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;prior&amp;amp;#32;learned&amp;amp;#32;or&amp;amp;#32;uncertain&amp;amp;#32;event;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;and&amp;amp;#32;reject&amp;amp;#32;the&amp;amp;#32;duplicate&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rc&amp;amp;#32;=&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;subprocess.TimeoutExpired:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;timeout;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;automatically&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_failed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;could&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;or&amp;amp;#32;local&amp;amp;#32;configuration&amp;amp;#32;was&amp;amp;#32;invalid&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;rc&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;rc&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;rc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;inspect&amp;amp;#32;server&amp;amp;#32;before&amp;amp;#32;any&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;reject_event(root,&amp;amp;#32;event_id,&amp;amp;#32;reason=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(reason)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;500:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;reason&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;rejected&amp;amp;quot;,&amp;amp;#32;reason=reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;list_events(root,&amp;amp;#32;state,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;((&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;(state,)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;directory.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;directory.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;notice&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;limit&amp;amp;#32;reached&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;limit&amp;amp;quot;:&amp;amp;#32;limit})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(entry)&amp;amp;#32;as&amp;amp;#32;item:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(item.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;metadata.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;id&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;state&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;review&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(row)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;state&amp;amp;quot;:&amp;amp;#32;name,&amp;amp;#32;&amp;amp;quot;error&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;#32;event;&amp;amp;#32;manual&amp;amp;#32;inspection&amp;amp;#32;required&amp;amp;quot;})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;show_event(root,&amp;amp;#32;event_id,&amp;amp;#32;state,&amp;amp;#32;path_only):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;state,&amp;amp;#32;check_id(event_id))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path_only:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;message_path&amp;amp;quot;:&amp;amp;#32;str(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;)})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;headers&amp;amp;#32;=&amp;amp;#32;BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message),&amp;amp;#32;262144)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;metadata&amp;amp;quot;:&amp;amp;#32;metadata,&amp;amp;#32;&amp;amp;quot;headers&amp;amp;quot;:&amp;amp;#32;{name:&amp;amp;#32;[str(v)[:2000]&amp;amp;#32;for&amp;amp;#32;v&amp;amp;#32;in&amp;amp;#32;headers.get_all(name,&amp;amp;#32;[])[:10]]&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;HEADERS}})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;review&amp;amp;quot;:&amp;amp;#32;receipt_for(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;warn_processed_storage(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;event&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event.name)&amp;amp;#32;or&amp;amp;#32;event.is_symlink()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;event.is_dir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;prefix&amp;amp;#32;in&amp;amp;#32;(event,&amp;amp;#32;event&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prefix.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(prefix&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_BYTES&amp;amp;#32;or&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;WARNING&amp;amp;#32;processed&amp;amp;#32;archive&amp;amp;#32;reached&amp;amp;#32;512&amp;amp;#32;MiB&amp;amp;#32;or&amp;amp;#32;1024&amp;amp;#32;events;&amp;amp;#32;review&amp;amp;#32;retention&amp;amp;#32;manually;&amp;amp;#32;nothing&amp;amp;#32;deleted&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sub&amp;amp;#32;=&amp;amp;#32;parser.add_subparsers(dest=&amp;amp;quot;command&amp;amp;quot;,&amp;amp;#32;required=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;list&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;JSON&amp;amp;#32;lines,&amp;amp;#32;metadata&amp;amp;#32;only&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--limit&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=100)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;show&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;selected&amp;amp;#32;headers&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;dumps&amp;amp;#32;the&amp;amp;#32;body&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--path&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;learn&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;explicitly&amp;amp;#32;train&amp;amp;#32;one&amp;amp;#32;reviewed&amp;amp;#32;event,&amp;amp;#32;then&amp;amp;#32;archive&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;help=&amp;amp;quot;root-only&amp;amp;#32;plaintext&amp;amp;#32;secret&amp;amp;#32;file;&amp;amp;#32;use&amp;amp;#32;--no-password&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;an&amp;amp;#32;intentionally&amp;amp;#32;trusted&amp;amp;#32;local&amp;amp;#32;controller&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--no-password&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;reject&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;archive&amp;amp;#32;one&amp;amp;#32;event&amp;amp;#32;without&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;deleting&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;--reason&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;root&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn_processed_storage(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;list&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.limit&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;10000:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;limit&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;1&amp;amp;#32;and&amp;amp;#32;10000&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;list_events(root,&amp;amp;#32;args.state,&amp;amp;#32;args.limit)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;show&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;show_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.state,&amp;amp;#32;args.path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;reject&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(reject_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.reason))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;timeout&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;0&amp;amp;#32;and&amp;amp;#32;300&amp;amp;#32;seconds&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;learn_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.label,&amp;amp;#32;executable=args.rspamc_bin,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;controller=args.controller,&amp;amp;#32;password_file=None&amp;amp;#32;if&amp;amp;#32;args.no_password&amp;amp;#32;else&amp;amp;#32;args.password_file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timeout=args.timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(result)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&amp;amp;#32;if&amp;amp;#32;result[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;operation&amp;amp;#32;refused&amp;amp;#32;or&amp;amp;#32;failed;&amp;amp;#32;inspect&amp;amp;#32;configuration/event&amp;amp;#32;locally&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;background&amp;amp;#32;learning&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;bounded&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback&amp;amp;#32;queue.&lt;br /&gt;
&lt;br /&gt;
The&amp;amp;#32;collector&amp;amp;#32;remains&amp;amp;#32;a&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;writer.&amp;amp;#32;This&amp;amp;#32;process&amp;amp;#32;owns&amp;amp;#32;controller&lt;br /&gt;
credentials,&amp;amp;#32;learns&amp;amp;#32;only&amp;amp;#32;validated&amp;amp;#32;INBOX/Junk&amp;amp;#32;events,&amp;amp;#32;preserves&amp;amp;#32;every&amp;amp;#32;original,&lt;br /&gt;
and&amp;amp;#32;never&amp;amp;#32;retries&amp;amp;#32;a&amp;amp;#32;training&amp;amp;#32;operation&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result.&amp;amp;#32;Run&amp;amp;#32;foreground&lt;br /&gt;
under&amp;amp;#32;FreeBSD&amp;amp;#32;daemon(8),&amp;amp;#32;or&amp;amp;#32;use&amp;amp;#32;--once&amp;amp;#32;for&amp;amp;#32;one&amp;amp;#32;bounded&amp;amp;#32;batch.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;signal&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;threading&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
&lt;br /&gt;
_spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_worker_admin&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback-admin.py&amp;amp;quot;))&lt;br /&gt;
admin&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;admin.FeedbackError&lt;br /&gt;
SHA_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{64}\Z&amp;amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
UNCERTAIN&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;log_event(kind,&amp;amp;#32;**fields):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Callers&amp;amp;#32;pass&amp;amp;#32;only&amp;amp;#32;fixed&amp;amp;#32;diagnostics,&amp;amp;#32;numeric&amp;amp;#32;counts,&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;classes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps({&amp;amp;quot;event&amp;amp;quot;:&amp;amp;#32;kind,&amp;amp;#32;**fields},&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True),&amp;amp;#32;flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_timestamp_ns(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(value,&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;missing&amp;amp;#32;timestamp&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parsed&amp;amp;#32;=&amp;amp;#32;datetime.fromisoformat(value.replace(&amp;amp;quot;Z&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;+00:00&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;parsed.tzinfo&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;timestamp&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;timezone&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;int(parsed.timestamp()&amp;amp;#32;*&amp;amp;#32;1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;order_events(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;bounded&amp;amp;#32;metadata,&amp;amp;#32;ordered&amp;amp;#32;by&amp;amp;#32;collector&amp;amp;#32;created_ns,&amp;amp;#32;then&amp;amp;#32;event&amp;amp;#32;ID.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;Legacy&amp;amp;#32;second-resolution&amp;amp;#32;timestamps&amp;amp;#32;are&amp;amp;#32;permitted&amp;amp;#32;only&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;same&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;contradictory&amp;amp;#32;labels&amp;amp;#32;at&amp;amp;#32;an&amp;amp;#32;identical&amp;amp;#32;timestamp.&amp;amp;#32;Such&amp;amp;#32;a&amp;amp;#32;tie&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cannot&amp;amp;#32;be&amp;amp;#32;resolved&amp;amp;#32;from&amp;amp;#32;random&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;is&amp;amp;#32;quarantined&amp;amp;#32;without&amp;amp;#32;learning.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pending&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows&amp;amp;#32;=&amp;amp;#32;[]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(pending)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;queue&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending&amp;amp;#32;/&amp;amp;#32;entry.name)&amp;amp;#32;as&amp;amp;#32;directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(directory.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;dict(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;sha256&amp;amp;quot;),&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(metadata[&amp;amp;quot;sha256&amp;amp;quot;]):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timestamp&amp;amp;#32;=&amp;amp;#32;metadata.get(&amp;amp;quot;created_ns&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;int)&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;bool)&amp;amp;#32;and&amp;amp;#32;timestamp&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;timestamp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;metadata:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(metadata.get(&amp;amp;quot;created_utc&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;manual&amp;amp;#32;administrator&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;archived&amp;amp;#32;this&amp;amp;#32;candidate.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;_invalid&amp;amp;quot;:&amp;amp;#32;True,&amp;amp;#32;&amp;amp;quot;_order_ns&amp;amp;quot;:&amp;amp;#32;0})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied&amp;amp;#32;=&amp;amp;#32;{}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied.setdefault((row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;]),&amp;amp;#32;set()).add(row[&amp;amp;quot;label&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;)&amp;amp;#32;and&amp;amp;#32;len(tied[(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;])])&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;sorted(rows,&amp;amp;#32;key=lambda&amp;amp;#32;row:&amp;amp;#32;(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;id&amp;amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_journal(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;root-owned&amp;amp;#32;receipts;&amp;amp;#32;successful&amp;amp;#32;classes&amp;amp;#32;use&amp;amp;#32;durable&amp;amp;#32;worker&amp;amp;#32;sequence.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked&amp;amp;#32;=&amp;amp;#32;{},&amp;amp;#32;set()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;used_bytes&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(processed)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;MAX_JOURNAL_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;processed&amp;amp;#32;/&amp;amp;#32;entry.name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.receipt_for(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(receipt,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;receipt.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;receipt&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(digest,&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(digest):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;worker_sequence&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(sequence,&amp;amp;#32;int)&amp;amp;#32;or&amp;amp;#32;isinstance(sequence,&amp;amp;#32;bool)&amp;amp;#32;or&amp;amp;#32;sequence&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;sequence&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;max(max_sequence,&amp;amp;#32;sequence)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;in&amp;amp;#32;UNCERTAIN:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;selected_class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;learned&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Existing&amp;amp;#32;manual&amp;amp;#32;receipts&amp;amp;#32;predate&amp;amp;#32;automatic&amp;amp;#32;processing;&amp;amp;#32;the&amp;amp;#32;old&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;admin&amp;amp;#32;refuses&amp;amp;#32;further&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;a&amp;amp;#32;SHA&amp;amp;#32;once&amp;amp;#32;it&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;receipt.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fallback&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(receipt.get(&amp;amp;quot;finished_utc&amp;amp;quot;,&amp;amp;#32;receipt.get(&amp;amp;quot;reviewed_utc&amp;amp;quot;)))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;position&amp;amp;#32;=&amp;amp;#32;(bool(sequence),&amp;amp;#32;sequence&amp;amp;#32;if&amp;amp;#32;sequence&amp;amp;#32;else&amp;amp;#32;fallback,&amp;amp;#32;entry.name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;or&amp;amp;#32;position&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;latest[digest][0]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;(position,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Count&amp;amp;#32;snapshots&amp;amp;#32;and&amp;amp;#32;preserved&amp;amp;#32;originals,&amp;amp;#32;never&amp;amp;#32;recursively&amp;amp;#32;walk&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;arbitrary&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail-user-owned&amp;amp;#32;source/&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;part&amp;amp;#32;in&amp;amp;#32;(directory,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(part)&amp;amp;#32;as&amp;amp;#32;safe:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;safe.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.stat(name,&amp;amp;#32;dir_fd=safe.fd,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(part&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_check_trusted_parent_chain(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;parent&amp;amp;#32;in&amp;amp;#32;Path(path).parents:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;link_info&amp;amp;#32;=&amp;amp;#32;parent.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;parent.stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(link_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;or&amp;amp;#32;info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;executable&amp;amp;#32;parent&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;controlled&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;resolve_executable(executable):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Allow&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;symlinks,&amp;amp;#32;but&amp;amp;#32;pin&amp;amp;#32;both&amp;amp;#32;operations&amp;amp;#32;to&amp;amp;#32;their&amp;amp;#32;target.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;source.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source_info&amp;amp;#32;=&amp;amp;#32;source.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;source_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;entry&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(source)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target&amp;amp;#32;=&amp;amp;#32;source.resolve(strict=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;target.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(target)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;controller_ready(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Authenticated&amp;amp;#32;read-only&amp;amp;#32;controller&amp;amp;#32;probe;&amp;amp;#32;no&amp;amp;#32;candidate&amp;amp;#32;is&amp;amp;#32;archived&amp;amp;#32;yet.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[resolve_executable(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unsafe&amp;amp;#32;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;stat&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;stdin=subprocess.DEVNULL,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;check=False,&amp;amp;#32;timeout=min(timeout,&amp;amp;#32;5)).returncode&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;process_pending(root,&amp;amp;#32;*,&amp;amp;#32;executable=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;,&amp;amp;#32;controller=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=admin.DEFAULT_PASSWORD_FILE,&amp;amp;#32;timeout=30,&amp;amp;#32;batch_size=20,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight=None,&amp;amp;#32;learner=None,&amp;amp;#32;stop_event=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Process&amp;amp;#32;at&amp;amp;#32;most&amp;amp;#32;batch_size&amp;amp;#32;candidates.&amp;amp;#32;Inject&amp;amp;#32;probes/learner&amp;amp;#32;for&amp;amp;#32;unit&amp;amp;#32;tests.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;One&amp;amp;#32;admin&amp;amp;#32;lock&amp;amp;#32;serializes&amp;amp;#32;manual&amp;amp;#32;and&amp;amp;#32;background&amp;amp;#32;operations.&amp;amp;#32;A&amp;amp;#32;crash&amp;amp;#32;after&amp;amp;#32;a&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;durable&amp;amp;#32;learning&amp;amp;#32;intent&amp;amp;#32;leaves&amp;amp;#32;status=learning,&amp;amp;#32;which&amp;amp;#32;blocks&amp;amp;#32;this&amp;amp;#32;SHA&amp;amp;#32;on&amp;amp;#32;the&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;next&amp;amp;#32;pass;&amp;amp;#32;subsequent&amp;amp;#32;candidates&amp;amp;#32;are&amp;amp;#32;preserved&amp;amp;#32;as&amp;amp;#32;quarantined_uncertain.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight&amp;amp;#32;=&amp;amp;#32;controller_ready&amp;amp;#32;if&amp;amp;#32;preflight&amp;amp;#32;is&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;preflight&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;learner&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;pkg&amp;amp;#32;installs&amp;amp;#32;rspamc&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;root-owned&amp;amp;#32;versioned&amp;amp;#32;symlink.&amp;amp;#32;Resolve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;it&amp;amp;#32;once&amp;amp;#32;so&amp;amp;#32;preflight&amp;amp;#32;and&amp;amp;#32;training&amp;amp;#32;invoke&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;trusted&amp;amp;#32;executable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;resolve_executable(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learner&amp;amp;#32;=&amp;amp;#32;admin.run_learning&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;learned&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;quot;:&amp;amp;#32;0,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;controller_unavailable&amp;amp;quot;:&amp;amp;#32;False,&amp;amp;#32;&amp;amp;quot;storage_blocked&amp;amp;quot;:&amp;amp;#32;False,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;archive_warning&amp;amp;quot;:&amp;amp;#32;False}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;events&amp;amp;#32;=&amp;amp;#32;order_events(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;len(events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;archive_count&amp;amp;#32;=&amp;amp;#32;_journal(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;preflight(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;candidate&amp;amp;#32;in&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;handled&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;batch_size&amp;amp;#32;or&amp;amp;#32;(stop_event&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;and&amp;amp;#32;stop_event.is_set()):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;candidate[&amp;amp;quot;id&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id).exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Crash&amp;amp;#32;before&amp;amp;#32;prepare_archive&amp;amp;#32;moved&amp;amp;#32;pending/ID:&amp;amp;#32;its&amp;amp;#32;root-owned&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;intent&amp;amp;#32;is&amp;amp;#32;authoritative.&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;pending&amp;amp;#32;original&amp;amp;#32;for&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;manual&amp;amp;#32;recovery&amp;amp;#32;and&amp;amp;#32;continue&amp;amp;#32;independent&amp;amp;#32;content,&amp;amp;#32;never&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;admin.read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;act&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;queued&amp;amp;#32;ordering/identity&amp;amp;#32;changed&amp;amp;#32;since&amp;amp;#32;enumeration.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;any(metadata.get(key)&amp;amp;#32;!=&amp;amp;#32;candidate.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(&amp;amp;quot;sha256&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;+&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label,&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;metadata[&amp;amp;quot;label&amp;amp;quot;],&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_ordering_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;event&amp;amp;#32;ordering&amp;amp;#32;is&amp;amp;#32;ambiguous&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;blocked:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;a&amp;amp;#32;prior&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;#32;has&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;and&amp;amp;#32;latest[digest][1]&amp;amp;#32;==&amp;amp;#32;label:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;same&amp;amp;#32;class&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;latest&amp;amp;#32;successful&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;automatic&amp;amp;#32;explicit&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label,&amp;amp;#32;reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;prepare_archive&amp;amp;#32;writes&amp;amp;#32;a&amp;amp;#32;root&amp;amp;#32;journal&amp;amp;#32;before&amp;amp;#32;moving&amp;amp;#32;the&amp;amp;#32;source;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;stop&amp;amp;#32;instead&amp;amp;#32;of&amp;amp;#32;retrying&amp;amp;#32;an&amp;amp;#32;incompletely&amp;amp;#32;prepared&amp;amp;#32;operation.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt.update({&amp;amp;quot;mode&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;automatic_imap_feedback&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;worker_sequence&amp;amp;quot;:&amp;amp;#32;sequence,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;processed_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&amp;amp;#32;&amp;amp;quot;source_created_ns&amp;amp;quot;:&amp;amp;#32;candidate[&amp;amp;quot;_order_ns&amp;amp;quot;]})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;previous_successful_class&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;latest[digest][1]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Durable&amp;amp;#32;intent&amp;amp;#32;exists&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;call&amp;amp;#32;that&amp;amp;#32;may&amp;amp;#32;change&amp;amp;#32;Bayes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;returncode&amp;amp;#32;=&amp;amp;#32;learner(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;failure;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;returncode&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;admin.collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;((True,&amp;amp;#32;sequence,&amp;amp;#32;event_id),&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;learned&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;duplicates&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive_count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;-=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;feedback_processed&amp;amp;quot;,&amp;amp;#32;id=event_id,&amp;amp;#32;label=label,&amp;amp;#32;status=receipt[&amp;amp;quot;status&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;controller&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;failed&amp;amp;#32;after&amp;amp;#32;the&amp;amp;#32;preflight.&amp;amp;#32;Leave&amp;amp;#32;all&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;other&amp;amp;#32;candidates&amp;amp;#32;pending&amp;amp;#32;until&amp;amp;#32;a&amp;amp;#32;fresh&amp;amp;#32;read-only&amp;amp;#32;probe&amp;amp;#32;passes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--poll&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--batch-size&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=20)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--once&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0.2&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.poll&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;60&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.batch_size&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;100:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;worker&amp;amp;#32;options&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping&amp;amp;#32;=&amp;amp;#32;threading.Event()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;signum&amp;amp;#32;in&amp;amp;#32;(signal.SIGTERM,&amp;amp;#32;signal.SIGINT):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;signal.signal(signum,&amp;amp;#32;lambda&amp;amp;#32;*_:&amp;amp;#32;stopping.set())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;lockfd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.worker.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(lockfd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;not&amp;amp;#32;stopping.is_set():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;process_pending(root,&amp;amp;#32;executable=args.rspamc_bin,&amp;amp;#32;controller=args.controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=args.password_file,&amp;amp;#32;timeout=args.timeout,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;batch_size=args.batch_size,&amp;amp;#32;stop_event=stopping)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;state&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;value&amp;amp;#32;for&amp;amp;#32;key,&amp;amp;#32;value&amp;amp;#32;in&amp;amp;#32;summary.items()&amp;amp;#32;if&amp;amp;#32;key&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;!=&amp;amp;#32;previous_state&amp;amp;#32;or&amp;amp;#32;any(summary[key]&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_state&amp;amp;quot;,&amp;amp;#32;**summary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;state&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;2&amp;amp;#32;if&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;or&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;else&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;previous_state&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_error&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;local&amp;amp;#32;queue,&amp;amp;#32;journal&amp;amp;#32;or&amp;amp;#32;configuration&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;#32;of&amp;amp;#32;uncertain&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping.wait(args.poll)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_start_failed&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;root,&amp;amp;#32;configuration&amp;amp;#32;or&amp;amp;#32;worker&amp;amp;#32;lock&amp;amp;#32;required&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(1)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=680</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=680"/>
		<updated>2026-10-29T23:24:00Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 2. Проверить систему без изменений */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;25&amp;amp;#32;/&amp;amp;#32;STARTTLS&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;465&amp;amp;#32;TLS&amp;amp;#32;или&amp;amp;#32;587&amp;amp;#32;STARTTLS&amp;amp;#32;+&amp;amp;#32;AUTH&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;LMTP&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;/opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;AUTH&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;файловый&amp;amp;#32;каталог&amp;amp;#32;пользователей&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;993&amp;amp;#32;TLS&amp;amp;#32;→&amp;amp;#32;Dovecot&lt;br /&gt;
SnappyMail&amp;amp;#32;(необязательно)&amp;amp;#32;→&amp;amp;#32;те&amp;amp;#32;же&amp;amp;#32;IMAPS&amp;amp;#32;и&amp;amp;#32;SMTP&amp;amp;#32;AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; copy&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sysrc&amp;amp;#32;hostname=mail.example.org&lt;br /&gt;
hostname&amp;amp;#32;mail.example.org&lt;br /&gt;
sysrc&amp;amp;#32;defaultrouter=198.51.100.1&lt;br /&gt;
#&amp;amp;#32;Только&amp;amp;#32;если&amp;amp;#32;в&amp;amp;#32;работающей&amp;amp;#32;таблице&amp;amp;#32;отсутствует&amp;amp;#32;default&amp;amp;#32;route:&lt;br /&gt;
route&amp;amp;#32;add&amp;amp;#32;default&amp;amp;#32;198.51.100.1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo&amp;amp;#32;/dev/da1&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;{print&amp;amp;#32;$3}&#039;)&lt;br /&gt;
test&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;-gt&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;/dev/da1&amp;amp;#32;/dev/zero&lt;br /&gt;
printf&amp;amp;#32;&#039;cmp&amp;amp;#32;status:&amp;amp;#32;%s\n&#039;&amp;amp;#32;&amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;update&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
#&amp;amp;#32;После&amp;amp;#32;проверки&amp;amp;#32;плана:&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
sysrc&amp;amp;#32;exim_enable=NO&amp;amp;#32;dovecot_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_enable=NONE&amp;amp;#32;sendmail_submit_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_outbound_enable=NO&amp;amp;#32;sendmail_msp_queue_enable=NO&lt;br /&gt;
service&amp;amp;#32;sendmail&amp;amp;#32;onestop&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/dovecot&amp;amp;#32;--version&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;mailnull&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;mail&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;review&amp;amp;#32;and&amp;amp;#32;explicit&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback.&lt;br /&gt;
&lt;br /&gt;
No&amp;amp;#32;command&amp;amp;#32;trains&amp;amp;#32;automatically.&amp;amp;#32;list/show&amp;amp;#32;are&amp;amp;#32;local&amp;amp;#32;reads.&amp;amp;#32;learn&amp;amp;#32;ID&amp;amp;#32;spam|ham&lt;br /&gt;
is&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;command&amp;amp;#32;that&amp;amp;#32;invokes&amp;amp;#32;rspamc.&amp;amp;#32;reject&amp;amp;#32;ID&amp;amp;#32;archives&amp;amp;#32;without&amp;amp;#32;training.&lt;br /&gt;
Queue&amp;amp;#32;root&amp;amp;#32;and&amp;amp;#32;processed&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;non-writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users;&lt;br /&gt;
pending&amp;amp;#32;is&amp;amp;#32;separately&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;vmail,&amp;amp;#32;mode&amp;amp;#32;0700.&amp;amp;#32;Archived&amp;amp;#32;originals&amp;amp;#32;remain&amp;amp;#32;in&lt;br /&gt;
processed/ID/source;&amp;amp;#32;only&amp;amp;#32;root-owned&amp;amp;#32;snapshots&amp;amp;#32;are&amp;amp;#32;used&amp;amp;#32;for&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;review.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;email&amp;amp;#32;import&amp;amp;#32;policy&lt;br /&gt;
from&amp;amp;#32;email.parser&amp;amp;#32;import&amp;amp;#32;BytesHeaderParser&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
&lt;br /&gt;
spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_collector&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback.py&amp;amp;quot;))&lt;br /&gt;
collector&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;collector.FeedbackError&lt;br /&gt;
ID_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{32}\Z&amp;amp;quot;)&lt;br /&gt;
DEFAULT_ROOT&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback&amp;amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/usr/local/etc/rspamd/controller.password&amp;amp;quot;&lt;br /&gt;
HEADERS&amp;amp;#32;=&amp;amp;#32;(&amp;amp;quot;From&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;To&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Date&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Subject&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Message-ID&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Status&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Score&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Rspamd-Action&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;emit(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;require_root():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;hasattr(os,&amp;amp;#32;&amp;amp;quot;geteuid&amp;amp;quot;)&amp;amp;#32;or&amp;amp;#32;os.geteuid()&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;administrative&amp;amp;#32;command&amp;amp;#32;requires&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_id(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;ID&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;exactly&amp;amp;#32;32&amp;amp;#32;lowercase&amp;amp;#32;hexadecimal&amp;amp;#32;characters&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;Directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Nofollow&amp;amp;#32;directory&amp;amp;#32;descriptor&amp;amp;#32;on&amp;amp;#32;FreeBSD;&amp;amp;#32;portable&amp;amp;#32;fallback&amp;amp;#32;for&amp;amp;#32;local&amp;amp;#32;tests.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__init__(self,&amp;amp;#32;path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;self.path.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;expected&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;os.open(self.path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__enter__(self):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;self&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__exit__(self,&amp;amp;#32;*args):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;read(self,&amp;amp;#32;name,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;&amp;amp;quot;\\&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;.&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;..&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;internal&amp;amp;#32;filename&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(name,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW&amp;amp;#32;|&amp;amp;#32;os.O_NONBLOCK,&amp;amp;#32;dir_fd=self.fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;self.path&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;symlink&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;event&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;rb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;source:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.fstat(source.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;info.st_nlink&amp;amp;#32;!=&amp;amp;#32;1&amp;amp;#32;or&amp;amp;#32;info.st_size&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;bounded,&amp;amp;#32;single-link&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;source.read(limit&amp;amp;#32;+&amp;amp;#32;1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(result)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;exceeded&amp;amp;#32;the&amp;amp;#32;size&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;read_event(directory,&amp;amp;#32;event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;On&amp;amp;#32;POSIX&amp;amp;#32;open&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;relative&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;already&amp;amp;#32;opened&amp;amp;#32;parent&amp;amp;#32;descriptor.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;parent&amp;amp;#32;path&amp;amp;#32;is&amp;amp;#32;deployment-owned,&amp;amp;#32;and&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;event&amp;amp;#32;is&amp;amp;#32;rejected.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(Path(directory)&amp;amp;#32;/&amp;amp;#32;event_id)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;entry.read(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;MAX_MESSAGE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;user&amp;amp;quot;),&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;event_id&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;bytes&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;len(message)&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;hashlib.sha256(message).hexdigest():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;content&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;its&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata,&amp;amp;#32;message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write_private(path,&amp;amp;#32;value,&amp;amp;#32;*,&amp;amp;#32;json_value=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;flags,&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;json_value:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;value&amp;amp;#32;=&amp;amp;#32;(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;\n&amp;amp;quot;).encode()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(value)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;update_receipt(directory,&amp;amp;#32;receipt):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin-new.json&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(temporary,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.replace(temporary,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_root_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;Path(path).stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;administrative&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;group/other&amp;amp;#32;writable&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;receipt_for(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;find_prior(root,&amp;amp;#32;sha256):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;damaged&amp;amp;#32;journal&amp;amp;#32;must&amp;amp;#32;never&amp;amp;#32;silently&amp;amp;#32;allow&amp;amp;#32;repeated&amp;amp;#32;training.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;is&amp;amp;#32;incomplete;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;before&amp;amp;#32;more&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;==&amp;amp;#32;sha256&amp;amp;#32;and&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label=None,&amp;amp;#32;reason=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(root).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;collector.MIN_FREE_BYTES&amp;amp;#32;+&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;#32;to&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;reviewed&amp;amp;#32;snapshot&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;],&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;reviewed_utc&amp;amp;quot;:&amp;amp;#32;collector.utc_now(),&amp;amp;#32;&amp;amp;quot;status&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;preparing&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;selected_class&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;reason&amp;amp;quot;:&amp;amp;#32;reason}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;json.dumps(metadata,&amp;amp;#32;ensure_ascii=True).encode())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;complete&amp;amp;#32;original&amp;amp;#32;event.&amp;amp;#32;Never&amp;amp;#32;traverse&amp;amp;#32;or&amp;amp;#32;execute&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;source/.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id,&amp;amp;#32;archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;status&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;archive,&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;executable.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;path&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[str(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;rspamc&amp;amp;#32;reads&amp;amp;#32;the&amp;amp;#32;file&amp;amp;#32;itself;&amp;amp;#32;the&amp;amp;#32;secret&amp;amp;#32;never&amp;amp;#32;appears&amp;amp;#32;in&amp;amp;#32;argv&amp;amp;#32;or&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;learn_&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;-u/-d:&amp;amp;#32;one&amp;amp;#32;shared&amp;amp;#32;Bayes&amp;amp;#32;classifier.&amp;amp;#32;No&amp;amp;#32;shell&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;input=message,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;timeout=timeout,&amp;amp;#32;check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;learn_event(root,&amp;amp;#32;event_id,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;training&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;prior&amp;amp;#32;=&amp;amp;#32;find_prior(root,&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prior:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;prior&amp;amp;#32;learned&amp;amp;#32;or&amp;amp;#32;uncertain&amp;amp;#32;event;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;and&amp;amp;#32;reject&amp;amp;#32;the&amp;amp;#32;duplicate&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rc&amp;amp;#32;=&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;subprocess.TimeoutExpired:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;timeout;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;automatically&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_failed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;could&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;or&amp;amp;#32;local&amp;amp;#32;configuration&amp;amp;#32;was&amp;amp;#32;invalid&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;rc&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;rc&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;rc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;inspect&amp;amp;#32;server&amp;amp;#32;before&amp;amp;#32;any&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;reject_event(root,&amp;amp;#32;event_id,&amp;amp;#32;reason=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(reason)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;500:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;reason&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;rejected&amp;amp;quot;,&amp;amp;#32;reason=reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;list_events(root,&amp;amp;#32;state,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;((&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;(state,)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;directory.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;directory.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;notice&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;limit&amp;amp;#32;reached&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;limit&amp;amp;quot;:&amp;amp;#32;limit})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(entry)&amp;amp;#32;as&amp;amp;#32;item:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(item.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;metadata.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;id&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;state&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;review&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(row)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;state&amp;amp;quot;:&amp;amp;#32;name,&amp;amp;#32;&amp;amp;quot;error&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;#32;event;&amp;amp;#32;manual&amp;amp;#32;inspection&amp;amp;#32;required&amp;amp;quot;})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;show_event(root,&amp;amp;#32;event_id,&amp;amp;#32;state,&amp;amp;#32;path_only):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;state,&amp;amp;#32;check_id(event_id))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path_only:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;message_path&amp;amp;quot;:&amp;amp;#32;str(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;)})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;headers&amp;amp;#32;=&amp;amp;#32;BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message),&amp;amp;#32;262144)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;metadata&amp;amp;quot;:&amp;amp;#32;metadata,&amp;amp;#32;&amp;amp;quot;headers&amp;amp;quot;:&amp;amp;#32;{name:&amp;amp;#32;[str(v)[:2000]&amp;amp;#32;for&amp;amp;#32;v&amp;amp;#32;in&amp;amp;#32;headers.get_all(name,&amp;amp;#32;[])[:10]]&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;HEADERS}})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;review&amp;amp;quot;:&amp;amp;#32;receipt_for(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;warn_processed_storage(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;event&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event.name)&amp;amp;#32;or&amp;amp;#32;event.is_symlink()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;event.is_dir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;prefix&amp;amp;#32;in&amp;amp;#32;(event,&amp;amp;#32;event&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prefix.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(prefix&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_BYTES&amp;amp;#32;or&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;WARNING&amp;amp;#32;processed&amp;amp;#32;archive&amp;amp;#32;reached&amp;amp;#32;512&amp;amp;#32;MiB&amp;amp;#32;or&amp;amp;#32;1024&amp;amp;#32;events;&amp;amp;#32;review&amp;amp;#32;retention&amp;amp;#32;manually;&amp;amp;#32;nothing&amp;amp;#32;deleted&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sub&amp;amp;#32;=&amp;amp;#32;parser.add_subparsers(dest=&amp;amp;quot;command&amp;amp;quot;,&amp;amp;#32;required=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;list&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;JSON&amp;amp;#32;lines,&amp;amp;#32;metadata&amp;amp;#32;only&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--limit&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=100)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;show&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;selected&amp;amp;#32;headers&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;dumps&amp;amp;#32;the&amp;amp;#32;body&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--path&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;learn&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;explicitly&amp;amp;#32;train&amp;amp;#32;one&amp;amp;#32;reviewed&amp;amp;#32;event,&amp;amp;#32;then&amp;amp;#32;archive&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;help=&amp;amp;quot;root-only&amp;amp;#32;plaintext&amp;amp;#32;secret&amp;amp;#32;file;&amp;amp;#32;use&amp;amp;#32;--no-password&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;an&amp;amp;#32;intentionally&amp;amp;#32;trusted&amp;amp;#32;local&amp;amp;#32;controller&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--no-password&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;reject&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;archive&amp;amp;#32;one&amp;amp;#32;event&amp;amp;#32;without&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;deleting&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;--reason&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;root&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn_processed_storage(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;list&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.limit&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;10000:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;limit&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;1&amp;amp;#32;and&amp;amp;#32;10000&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;list_events(root,&amp;amp;#32;args.state,&amp;amp;#32;args.limit)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;show&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;show_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.state,&amp;amp;#32;args.path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;reject&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(reject_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.reason))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;timeout&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;0&amp;amp;#32;and&amp;amp;#32;300&amp;amp;#32;seconds&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;learn_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.label,&amp;amp;#32;executable=args.rspamc_bin,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;controller=args.controller,&amp;amp;#32;password_file=None&amp;amp;#32;if&amp;amp;#32;args.no_password&amp;amp;#32;else&amp;amp;#32;args.password_file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timeout=args.timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(result)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&amp;amp;#32;if&amp;amp;#32;result[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;operation&amp;amp;#32;refused&amp;amp;#32;or&amp;amp;#32;failed;&amp;amp;#32;inspect&amp;amp;#32;configuration/event&amp;amp;#32;locally&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;background&amp;amp;#32;learning&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;bounded&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback&amp;amp;#32;queue.&lt;br /&gt;
&lt;br /&gt;
The&amp;amp;#32;collector&amp;amp;#32;remains&amp;amp;#32;a&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;writer.&amp;amp;#32;This&amp;amp;#32;process&amp;amp;#32;owns&amp;amp;#32;controller&lt;br /&gt;
credentials,&amp;amp;#32;learns&amp;amp;#32;only&amp;amp;#32;validated&amp;amp;#32;INBOX/Junk&amp;amp;#32;events,&amp;amp;#32;preserves&amp;amp;#32;every&amp;amp;#32;original,&lt;br /&gt;
and&amp;amp;#32;never&amp;amp;#32;retries&amp;amp;#32;a&amp;amp;#32;training&amp;amp;#32;operation&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result.&amp;amp;#32;Run&amp;amp;#32;foreground&lt;br /&gt;
under&amp;amp;#32;FreeBSD&amp;amp;#32;daemon(8),&amp;amp;#32;or&amp;amp;#32;use&amp;amp;#32;--once&amp;amp;#32;for&amp;amp;#32;one&amp;amp;#32;bounded&amp;amp;#32;batch.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;signal&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;threading&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
&lt;br /&gt;
_spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_worker_admin&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback-admin.py&amp;amp;quot;))&lt;br /&gt;
admin&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;admin.FeedbackError&lt;br /&gt;
SHA_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{64}\Z&amp;amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
UNCERTAIN&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;log_event(kind,&amp;amp;#32;**fields):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Callers&amp;amp;#32;pass&amp;amp;#32;only&amp;amp;#32;fixed&amp;amp;#32;diagnostics,&amp;amp;#32;numeric&amp;amp;#32;counts,&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;classes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps({&amp;amp;quot;event&amp;amp;quot;:&amp;amp;#32;kind,&amp;amp;#32;**fields},&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True),&amp;amp;#32;flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_timestamp_ns(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(value,&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;missing&amp;amp;#32;timestamp&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parsed&amp;amp;#32;=&amp;amp;#32;datetime.fromisoformat(value.replace(&amp;amp;quot;Z&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;+00:00&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;parsed.tzinfo&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;timestamp&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;timezone&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;int(parsed.timestamp()&amp;amp;#32;*&amp;amp;#32;1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;order_events(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;bounded&amp;amp;#32;metadata,&amp;amp;#32;ordered&amp;amp;#32;by&amp;amp;#32;collector&amp;amp;#32;created_ns,&amp;amp;#32;then&amp;amp;#32;event&amp;amp;#32;ID.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;Legacy&amp;amp;#32;second-resolution&amp;amp;#32;timestamps&amp;amp;#32;are&amp;amp;#32;permitted&amp;amp;#32;only&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;same&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;contradictory&amp;amp;#32;labels&amp;amp;#32;at&amp;amp;#32;an&amp;amp;#32;identical&amp;amp;#32;timestamp.&amp;amp;#32;Such&amp;amp;#32;a&amp;amp;#32;tie&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cannot&amp;amp;#32;be&amp;amp;#32;resolved&amp;amp;#32;from&amp;amp;#32;random&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;is&amp;amp;#32;quarantined&amp;amp;#32;without&amp;amp;#32;learning.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pending&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows&amp;amp;#32;=&amp;amp;#32;[]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(pending)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;queue&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending&amp;amp;#32;/&amp;amp;#32;entry.name)&amp;amp;#32;as&amp;amp;#32;directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(directory.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;dict(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;sha256&amp;amp;quot;),&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(metadata[&amp;amp;quot;sha256&amp;amp;quot;]):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timestamp&amp;amp;#32;=&amp;amp;#32;metadata.get(&amp;amp;quot;created_ns&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;int)&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;bool)&amp;amp;#32;and&amp;amp;#32;timestamp&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;timestamp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;metadata:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(metadata.get(&amp;amp;quot;created_utc&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;manual&amp;amp;#32;administrator&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;archived&amp;amp;#32;this&amp;amp;#32;candidate.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;_invalid&amp;amp;quot;:&amp;amp;#32;True,&amp;amp;#32;&amp;amp;quot;_order_ns&amp;amp;quot;:&amp;amp;#32;0})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied&amp;amp;#32;=&amp;amp;#32;{}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied.setdefault((row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;]),&amp;amp;#32;set()).add(row[&amp;amp;quot;label&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;)&amp;amp;#32;and&amp;amp;#32;len(tied[(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;])])&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;sorted(rows,&amp;amp;#32;key=lambda&amp;amp;#32;row:&amp;amp;#32;(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;id&amp;amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_journal(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;root-owned&amp;amp;#32;receipts;&amp;amp;#32;successful&amp;amp;#32;classes&amp;amp;#32;use&amp;amp;#32;durable&amp;amp;#32;worker&amp;amp;#32;sequence.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked&amp;amp;#32;=&amp;amp;#32;{},&amp;amp;#32;set()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;used_bytes&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(processed)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;MAX_JOURNAL_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;processed&amp;amp;#32;/&amp;amp;#32;entry.name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.receipt_for(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(receipt,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;receipt.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;receipt&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(digest,&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(digest):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;worker_sequence&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(sequence,&amp;amp;#32;int)&amp;amp;#32;or&amp;amp;#32;isinstance(sequence,&amp;amp;#32;bool)&amp;amp;#32;or&amp;amp;#32;sequence&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;sequence&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;max(max_sequence,&amp;amp;#32;sequence)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;in&amp;amp;#32;UNCERTAIN:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;selected_class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;learned&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Existing&amp;amp;#32;manual&amp;amp;#32;receipts&amp;amp;#32;predate&amp;amp;#32;automatic&amp;amp;#32;processing;&amp;amp;#32;the&amp;amp;#32;old&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;admin&amp;amp;#32;refuses&amp;amp;#32;further&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;a&amp;amp;#32;SHA&amp;amp;#32;once&amp;amp;#32;it&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;receipt.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fallback&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(receipt.get(&amp;amp;quot;finished_utc&amp;amp;quot;,&amp;amp;#32;receipt.get(&amp;amp;quot;reviewed_utc&amp;amp;quot;)))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;position&amp;amp;#32;=&amp;amp;#32;(bool(sequence),&amp;amp;#32;sequence&amp;amp;#32;if&amp;amp;#32;sequence&amp;amp;#32;else&amp;amp;#32;fallback,&amp;amp;#32;entry.name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;or&amp;amp;#32;position&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;latest[digest][0]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;(position,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Count&amp;amp;#32;snapshots&amp;amp;#32;and&amp;amp;#32;preserved&amp;amp;#32;originals,&amp;amp;#32;never&amp;amp;#32;recursively&amp;amp;#32;walk&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;arbitrary&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail-user-owned&amp;amp;#32;source/&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;part&amp;amp;#32;in&amp;amp;#32;(directory,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(part)&amp;amp;#32;as&amp;amp;#32;safe:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;safe.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.stat(name,&amp;amp;#32;dir_fd=safe.fd,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(part&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_check_trusted_parent_chain(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;parent&amp;amp;#32;in&amp;amp;#32;Path(path).parents:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;link_info&amp;amp;#32;=&amp;amp;#32;parent.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;parent.stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(link_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;or&amp;amp;#32;info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;executable&amp;amp;#32;parent&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;controlled&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;resolve_executable(executable):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Allow&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;symlinks,&amp;amp;#32;but&amp;amp;#32;pin&amp;amp;#32;both&amp;amp;#32;operations&amp;amp;#32;to&amp;amp;#32;their&amp;amp;#32;target.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;source.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source_info&amp;amp;#32;=&amp;amp;#32;source.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;source_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;entry&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(source)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target&amp;amp;#32;=&amp;amp;#32;source.resolve(strict=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;target.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(target)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;controller_ready(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Authenticated&amp;amp;#32;read-only&amp;amp;#32;controller&amp;amp;#32;probe;&amp;amp;#32;no&amp;amp;#32;candidate&amp;amp;#32;is&amp;amp;#32;archived&amp;amp;#32;yet.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[resolve_executable(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unsafe&amp;amp;#32;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;stat&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;stdin=subprocess.DEVNULL,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;check=False,&amp;amp;#32;timeout=min(timeout,&amp;amp;#32;5)).returncode&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;process_pending(root,&amp;amp;#32;*,&amp;amp;#32;executable=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;,&amp;amp;#32;controller=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=admin.DEFAULT_PASSWORD_FILE,&amp;amp;#32;timeout=30,&amp;amp;#32;batch_size=20,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight=None,&amp;amp;#32;learner=None,&amp;amp;#32;stop_event=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Process&amp;amp;#32;at&amp;amp;#32;most&amp;amp;#32;batch_size&amp;amp;#32;candidates.&amp;amp;#32;Inject&amp;amp;#32;probes/learner&amp;amp;#32;for&amp;amp;#32;unit&amp;amp;#32;tests.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;One&amp;amp;#32;admin&amp;amp;#32;lock&amp;amp;#32;serializes&amp;amp;#32;manual&amp;amp;#32;and&amp;amp;#32;background&amp;amp;#32;operations.&amp;amp;#32;A&amp;amp;#32;crash&amp;amp;#32;after&amp;amp;#32;a&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;durable&amp;amp;#32;learning&amp;amp;#32;intent&amp;amp;#32;leaves&amp;amp;#32;status=learning,&amp;amp;#32;which&amp;amp;#32;blocks&amp;amp;#32;this&amp;amp;#32;SHA&amp;amp;#32;on&amp;amp;#32;the&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;next&amp;amp;#32;pass;&amp;amp;#32;subsequent&amp;amp;#32;candidates&amp;amp;#32;are&amp;amp;#32;preserved&amp;amp;#32;as&amp;amp;#32;quarantined_uncertain.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight&amp;amp;#32;=&amp;amp;#32;controller_ready&amp;amp;#32;if&amp;amp;#32;preflight&amp;amp;#32;is&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;preflight&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;learner&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;pkg&amp;amp;#32;installs&amp;amp;#32;rspamc&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;root-owned&amp;amp;#32;versioned&amp;amp;#32;symlink.&amp;amp;#32;Resolve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;it&amp;amp;#32;once&amp;amp;#32;so&amp;amp;#32;preflight&amp;amp;#32;and&amp;amp;#32;training&amp;amp;#32;invoke&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;trusted&amp;amp;#32;executable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;resolve_executable(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learner&amp;amp;#32;=&amp;amp;#32;admin.run_learning&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;learned&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;quot;:&amp;amp;#32;0,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;controller_unavailable&amp;amp;quot;:&amp;amp;#32;False,&amp;amp;#32;&amp;amp;quot;storage_blocked&amp;amp;quot;:&amp;amp;#32;False,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;archive_warning&amp;amp;quot;:&amp;amp;#32;False}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;events&amp;amp;#32;=&amp;amp;#32;order_events(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;len(events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;archive_count&amp;amp;#32;=&amp;amp;#32;_journal(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;preflight(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;candidate&amp;amp;#32;in&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;handled&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;batch_size&amp;amp;#32;or&amp;amp;#32;(stop_event&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;and&amp;amp;#32;stop_event.is_set()):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;candidate[&amp;amp;quot;id&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id).exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Crash&amp;amp;#32;before&amp;amp;#32;prepare_archive&amp;amp;#32;moved&amp;amp;#32;pending/ID:&amp;amp;#32;its&amp;amp;#32;root-owned&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;intent&amp;amp;#32;is&amp;amp;#32;authoritative.&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;pending&amp;amp;#32;original&amp;amp;#32;for&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;manual&amp;amp;#32;recovery&amp;amp;#32;and&amp;amp;#32;continue&amp;amp;#32;independent&amp;amp;#32;content,&amp;amp;#32;never&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;admin.read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;act&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;queued&amp;amp;#32;ordering/identity&amp;amp;#32;changed&amp;amp;#32;since&amp;amp;#32;enumeration.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;any(metadata.get(key)&amp;amp;#32;!=&amp;amp;#32;candidate.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(&amp;amp;quot;sha256&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;+&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label,&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;metadata[&amp;amp;quot;label&amp;amp;quot;],&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_ordering_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;event&amp;amp;#32;ordering&amp;amp;#32;is&amp;amp;#32;ambiguous&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;blocked:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;a&amp;amp;#32;prior&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;#32;has&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;and&amp;amp;#32;latest[digest][1]&amp;amp;#32;==&amp;amp;#32;label:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;same&amp;amp;#32;class&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;latest&amp;amp;#32;successful&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;automatic&amp;amp;#32;explicit&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label,&amp;amp;#32;reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;prepare_archive&amp;amp;#32;writes&amp;amp;#32;a&amp;amp;#32;root&amp;amp;#32;journal&amp;amp;#32;before&amp;amp;#32;moving&amp;amp;#32;the&amp;amp;#32;source;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;stop&amp;amp;#32;instead&amp;amp;#32;of&amp;amp;#32;retrying&amp;amp;#32;an&amp;amp;#32;incompletely&amp;amp;#32;prepared&amp;amp;#32;operation.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt.update({&amp;amp;quot;mode&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;automatic_imap_feedback&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;worker_sequence&amp;amp;quot;:&amp;amp;#32;sequence,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;processed_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&amp;amp;#32;&amp;amp;quot;source_created_ns&amp;amp;quot;:&amp;amp;#32;candidate[&amp;amp;quot;_order_ns&amp;amp;quot;]})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;previous_successful_class&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;latest[digest][1]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Durable&amp;amp;#32;intent&amp;amp;#32;exists&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;call&amp;amp;#32;that&amp;amp;#32;may&amp;amp;#32;change&amp;amp;#32;Bayes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;returncode&amp;amp;#32;=&amp;amp;#32;learner(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;failure;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;returncode&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;admin.collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;((True,&amp;amp;#32;sequence,&amp;amp;#32;event_id),&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;learned&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;duplicates&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive_count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;-=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;feedback_processed&amp;amp;quot;,&amp;amp;#32;id=event_id,&amp;amp;#32;label=label,&amp;amp;#32;status=receipt[&amp;amp;quot;status&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;controller&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;failed&amp;amp;#32;after&amp;amp;#32;the&amp;amp;#32;preflight.&amp;amp;#32;Leave&amp;amp;#32;all&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;other&amp;amp;#32;candidates&amp;amp;#32;pending&amp;amp;#32;until&amp;amp;#32;a&amp;amp;#32;fresh&amp;amp;#32;read-only&amp;amp;#32;probe&amp;amp;#32;passes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--poll&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--batch-size&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=20)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--once&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0.2&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.poll&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;60&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.batch_size&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;100:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;worker&amp;amp;#32;options&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping&amp;amp;#32;=&amp;amp;#32;threading.Event()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;signum&amp;amp;#32;in&amp;amp;#32;(signal.SIGTERM,&amp;amp;#32;signal.SIGINT):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;signal.signal(signum,&amp;amp;#32;lambda&amp;amp;#32;*_:&amp;amp;#32;stopping.set())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;lockfd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.worker.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(lockfd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;not&amp;amp;#32;stopping.is_set():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;process_pending(root,&amp;amp;#32;executable=args.rspamc_bin,&amp;amp;#32;controller=args.controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=args.password_file,&amp;amp;#32;timeout=args.timeout,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;batch_size=args.batch_size,&amp;amp;#32;stop_event=stopping)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;state&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;value&amp;amp;#32;for&amp;amp;#32;key,&amp;amp;#32;value&amp;amp;#32;in&amp;amp;#32;summary.items()&amp;amp;#32;if&amp;amp;#32;key&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;!=&amp;amp;#32;previous_state&amp;amp;#32;or&amp;amp;#32;any(summary[key]&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_state&amp;amp;quot;,&amp;amp;#32;**summary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;state&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;2&amp;amp;#32;if&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;or&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;else&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;previous_state&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_error&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;local&amp;amp;#32;queue,&amp;amp;#32;journal&amp;amp;#32;or&amp;amp;#32;configuration&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;#32;of&amp;amp;#32;uncertain&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping.wait(args.poll)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_start_failed&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;root,&amp;amp;#32;configuration&amp;amp;#32;or&amp;amp;#32;worker&amp;amp;#32;lock&amp;amp;#32;required&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(1)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=679</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=679"/>
		<updated>2026-10-29T23:18:49Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 2. Проверить систему без изменений */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;25&amp;amp;#32;/&amp;amp;#32;STARTTLS&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;465&amp;amp;#32;TLS&amp;amp;#32;или&amp;amp;#32;587&amp;amp;#32;STARTTLS&amp;amp;#32;+&amp;amp;#32;AUTH&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;LMTP&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;/opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;AUTH&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;файловый&amp;amp;#32;каталог&amp;amp;#32;пользователей&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;993&amp;amp;#32;TLS&amp;amp;#32;→&amp;amp;#32;Dovecot&lt;br /&gt;
SnappyMail&amp;amp;#32;(необязательно)&amp;amp;#32;→&amp;amp;#32;те&amp;amp;#32;же&amp;amp;#32;IMAPS&amp;amp;#32;и&amp;amp;#32;SMTP&amp;amp;#32;AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; line&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sysrc&amp;amp;#32;hostname=mail.example.org&lt;br /&gt;
hostname&amp;amp;#32;mail.example.org&lt;br /&gt;
sysrc&amp;amp;#32;defaultrouter=198.51.100.1&lt;br /&gt;
#&amp;amp;#32;Только&amp;amp;#32;если&amp;amp;#32;в&amp;amp;#32;работающей&amp;amp;#32;таблице&amp;amp;#32;отсутствует&amp;amp;#32;default&amp;amp;#32;route:&lt;br /&gt;
route&amp;amp;#32;add&amp;amp;#32;default&amp;amp;#32;198.51.100.1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo&amp;amp;#32;/dev/da1&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;{print&amp;amp;#32;$3}&#039;)&lt;br /&gt;
test&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;-gt&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;/dev/da1&amp;amp;#32;/dev/zero&lt;br /&gt;
printf&amp;amp;#32;&#039;cmp&amp;amp;#32;status:&amp;amp;#32;%s\n&#039;&amp;amp;#32;&amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;update&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
#&amp;amp;#32;После&amp;amp;#32;проверки&amp;amp;#32;плана:&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
sysrc&amp;amp;#32;exim_enable=NO&amp;amp;#32;dovecot_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_enable=NONE&amp;amp;#32;sendmail_submit_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_outbound_enable=NO&amp;amp;#32;sendmail_msp_queue_enable=NO&lt;br /&gt;
service&amp;amp;#32;sendmail&amp;amp;#32;onestop&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/dovecot&amp;amp;#32;--version&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;mailnull&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;mail&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;review&amp;amp;#32;and&amp;amp;#32;explicit&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback.&lt;br /&gt;
&lt;br /&gt;
No&amp;amp;#32;command&amp;amp;#32;trains&amp;amp;#32;automatically.&amp;amp;#32;list/show&amp;amp;#32;are&amp;amp;#32;local&amp;amp;#32;reads.&amp;amp;#32;learn&amp;amp;#32;ID&amp;amp;#32;spam|ham&lt;br /&gt;
is&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;command&amp;amp;#32;that&amp;amp;#32;invokes&amp;amp;#32;rspamc.&amp;amp;#32;reject&amp;amp;#32;ID&amp;amp;#32;archives&amp;amp;#32;without&amp;amp;#32;training.&lt;br /&gt;
Queue&amp;amp;#32;root&amp;amp;#32;and&amp;amp;#32;processed&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;non-writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users;&lt;br /&gt;
pending&amp;amp;#32;is&amp;amp;#32;separately&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;vmail,&amp;amp;#32;mode&amp;amp;#32;0700.&amp;amp;#32;Archived&amp;amp;#32;originals&amp;amp;#32;remain&amp;amp;#32;in&lt;br /&gt;
processed/ID/source;&amp;amp;#32;only&amp;amp;#32;root-owned&amp;amp;#32;snapshots&amp;amp;#32;are&amp;amp;#32;used&amp;amp;#32;for&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;review.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;email&amp;amp;#32;import&amp;amp;#32;policy&lt;br /&gt;
from&amp;amp;#32;email.parser&amp;amp;#32;import&amp;amp;#32;BytesHeaderParser&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
&lt;br /&gt;
spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_collector&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback.py&amp;amp;quot;))&lt;br /&gt;
collector&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;collector.FeedbackError&lt;br /&gt;
ID_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{32}\Z&amp;amp;quot;)&lt;br /&gt;
DEFAULT_ROOT&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback&amp;amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/usr/local/etc/rspamd/controller.password&amp;amp;quot;&lt;br /&gt;
HEADERS&amp;amp;#32;=&amp;amp;#32;(&amp;amp;quot;From&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;To&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Date&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Subject&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Message-ID&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Status&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Score&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Rspamd-Action&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;emit(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;require_root():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;hasattr(os,&amp;amp;#32;&amp;amp;quot;geteuid&amp;amp;quot;)&amp;amp;#32;or&amp;amp;#32;os.geteuid()&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;administrative&amp;amp;#32;command&amp;amp;#32;requires&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_id(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;ID&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;exactly&amp;amp;#32;32&amp;amp;#32;lowercase&amp;amp;#32;hexadecimal&amp;amp;#32;characters&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;Directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Nofollow&amp;amp;#32;directory&amp;amp;#32;descriptor&amp;amp;#32;on&amp;amp;#32;FreeBSD;&amp;amp;#32;portable&amp;amp;#32;fallback&amp;amp;#32;for&amp;amp;#32;local&amp;amp;#32;tests.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__init__(self,&amp;amp;#32;path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;self.path.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;expected&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;os.open(self.path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__enter__(self):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;self&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__exit__(self,&amp;amp;#32;*args):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;read(self,&amp;amp;#32;name,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;&amp;amp;quot;\\&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;.&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;..&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;internal&amp;amp;#32;filename&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(name,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW&amp;amp;#32;|&amp;amp;#32;os.O_NONBLOCK,&amp;amp;#32;dir_fd=self.fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;self.path&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;symlink&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;event&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;rb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;source:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.fstat(source.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;info.st_nlink&amp;amp;#32;!=&amp;amp;#32;1&amp;amp;#32;or&amp;amp;#32;info.st_size&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;bounded,&amp;amp;#32;single-link&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;source.read(limit&amp;amp;#32;+&amp;amp;#32;1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(result)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;exceeded&amp;amp;#32;the&amp;amp;#32;size&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;read_event(directory,&amp;amp;#32;event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;On&amp;amp;#32;POSIX&amp;amp;#32;open&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;relative&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;already&amp;amp;#32;opened&amp;amp;#32;parent&amp;amp;#32;descriptor.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;parent&amp;amp;#32;path&amp;amp;#32;is&amp;amp;#32;deployment-owned,&amp;amp;#32;and&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;event&amp;amp;#32;is&amp;amp;#32;rejected.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(Path(directory)&amp;amp;#32;/&amp;amp;#32;event_id)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;entry.read(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;MAX_MESSAGE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;user&amp;amp;quot;),&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;event_id&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;bytes&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;len(message)&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;hashlib.sha256(message).hexdigest():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;content&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;its&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata,&amp;amp;#32;message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write_private(path,&amp;amp;#32;value,&amp;amp;#32;*,&amp;amp;#32;json_value=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;flags,&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;json_value:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;value&amp;amp;#32;=&amp;amp;#32;(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;\n&amp;amp;quot;).encode()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(value)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;update_receipt(directory,&amp;amp;#32;receipt):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin-new.json&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(temporary,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.replace(temporary,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_root_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;Path(path).stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;administrative&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;group/other&amp;amp;#32;writable&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;receipt_for(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;find_prior(root,&amp;amp;#32;sha256):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;damaged&amp;amp;#32;journal&amp;amp;#32;must&amp;amp;#32;never&amp;amp;#32;silently&amp;amp;#32;allow&amp;amp;#32;repeated&amp;amp;#32;training.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;is&amp;amp;#32;incomplete;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;before&amp;amp;#32;more&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;==&amp;amp;#32;sha256&amp;amp;#32;and&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label=None,&amp;amp;#32;reason=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(root).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;collector.MIN_FREE_BYTES&amp;amp;#32;+&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;#32;to&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;reviewed&amp;amp;#32;snapshot&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;],&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;reviewed_utc&amp;amp;quot;:&amp;amp;#32;collector.utc_now(),&amp;amp;#32;&amp;amp;quot;status&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;preparing&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;selected_class&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;reason&amp;amp;quot;:&amp;amp;#32;reason}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;json.dumps(metadata,&amp;amp;#32;ensure_ascii=True).encode())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;complete&amp;amp;#32;original&amp;amp;#32;event.&amp;amp;#32;Never&amp;amp;#32;traverse&amp;amp;#32;or&amp;amp;#32;execute&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;source/.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id,&amp;amp;#32;archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;status&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;archive,&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;executable.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;path&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[str(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;rspamc&amp;amp;#32;reads&amp;amp;#32;the&amp;amp;#32;file&amp;amp;#32;itself;&amp;amp;#32;the&amp;amp;#32;secret&amp;amp;#32;never&amp;amp;#32;appears&amp;amp;#32;in&amp;amp;#32;argv&amp;amp;#32;or&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;learn_&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;-u/-d:&amp;amp;#32;one&amp;amp;#32;shared&amp;amp;#32;Bayes&amp;amp;#32;classifier.&amp;amp;#32;No&amp;amp;#32;shell&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;input=message,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;timeout=timeout,&amp;amp;#32;check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;learn_event(root,&amp;amp;#32;event_id,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;training&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;prior&amp;amp;#32;=&amp;amp;#32;find_prior(root,&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prior:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;prior&amp;amp;#32;learned&amp;amp;#32;or&amp;amp;#32;uncertain&amp;amp;#32;event;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;and&amp;amp;#32;reject&amp;amp;#32;the&amp;amp;#32;duplicate&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rc&amp;amp;#32;=&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;subprocess.TimeoutExpired:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;timeout;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;automatically&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_failed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;could&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;or&amp;amp;#32;local&amp;amp;#32;configuration&amp;amp;#32;was&amp;amp;#32;invalid&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;rc&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;rc&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;rc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;inspect&amp;amp;#32;server&amp;amp;#32;before&amp;amp;#32;any&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;reject_event(root,&amp;amp;#32;event_id,&amp;amp;#32;reason=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(reason)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;500:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;reason&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;rejected&amp;amp;quot;,&amp;amp;#32;reason=reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;list_events(root,&amp;amp;#32;state,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;((&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;(state,)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;directory.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;directory.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;notice&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;limit&amp;amp;#32;reached&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;limit&amp;amp;quot;:&amp;amp;#32;limit})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(entry)&amp;amp;#32;as&amp;amp;#32;item:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(item.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;metadata.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;id&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;state&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;review&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(row)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;state&amp;amp;quot;:&amp;amp;#32;name,&amp;amp;#32;&amp;amp;quot;error&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;#32;event;&amp;amp;#32;manual&amp;amp;#32;inspection&amp;amp;#32;required&amp;amp;quot;})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;show_event(root,&amp;amp;#32;event_id,&amp;amp;#32;state,&amp;amp;#32;path_only):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;state,&amp;amp;#32;check_id(event_id))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path_only:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;message_path&amp;amp;quot;:&amp;amp;#32;str(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;)})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;headers&amp;amp;#32;=&amp;amp;#32;BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message),&amp;amp;#32;262144)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;metadata&amp;amp;quot;:&amp;amp;#32;metadata,&amp;amp;#32;&amp;amp;quot;headers&amp;amp;quot;:&amp;amp;#32;{name:&amp;amp;#32;[str(v)[:2000]&amp;amp;#32;for&amp;amp;#32;v&amp;amp;#32;in&amp;amp;#32;headers.get_all(name,&amp;amp;#32;[])[:10]]&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;HEADERS}})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;review&amp;amp;quot;:&amp;amp;#32;receipt_for(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;warn_processed_storage(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;event&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event.name)&amp;amp;#32;or&amp;amp;#32;event.is_symlink()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;event.is_dir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;prefix&amp;amp;#32;in&amp;amp;#32;(event,&amp;amp;#32;event&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prefix.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(prefix&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_BYTES&amp;amp;#32;or&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;WARNING&amp;amp;#32;processed&amp;amp;#32;archive&amp;amp;#32;reached&amp;amp;#32;512&amp;amp;#32;MiB&amp;amp;#32;or&amp;amp;#32;1024&amp;amp;#32;events;&amp;amp;#32;review&amp;amp;#32;retention&amp;amp;#32;manually;&amp;amp;#32;nothing&amp;amp;#32;deleted&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sub&amp;amp;#32;=&amp;amp;#32;parser.add_subparsers(dest=&amp;amp;quot;command&amp;amp;quot;,&amp;amp;#32;required=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;list&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;JSON&amp;amp;#32;lines,&amp;amp;#32;metadata&amp;amp;#32;only&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--limit&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=100)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;show&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;selected&amp;amp;#32;headers&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;dumps&amp;amp;#32;the&amp;amp;#32;body&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--path&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;learn&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;explicitly&amp;amp;#32;train&amp;amp;#32;one&amp;amp;#32;reviewed&amp;amp;#32;event,&amp;amp;#32;then&amp;amp;#32;archive&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;help=&amp;amp;quot;root-only&amp;amp;#32;plaintext&amp;amp;#32;secret&amp;amp;#32;file;&amp;amp;#32;use&amp;amp;#32;--no-password&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;an&amp;amp;#32;intentionally&amp;amp;#32;trusted&amp;amp;#32;local&amp;amp;#32;controller&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--no-password&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;reject&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;archive&amp;amp;#32;one&amp;amp;#32;event&amp;amp;#32;without&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;deleting&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;--reason&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;root&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn_processed_storage(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;list&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.limit&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;10000:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;limit&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;1&amp;amp;#32;and&amp;amp;#32;10000&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;list_events(root,&amp;amp;#32;args.state,&amp;amp;#32;args.limit)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;show&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;show_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.state,&amp;amp;#32;args.path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;reject&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(reject_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.reason))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;timeout&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;0&amp;amp;#32;and&amp;amp;#32;300&amp;amp;#32;seconds&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;learn_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.label,&amp;amp;#32;executable=args.rspamc_bin,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;controller=args.controller,&amp;amp;#32;password_file=None&amp;amp;#32;if&amp;amp;#32;args.no_password&amp;amp;#32;else&amp;amp;#32;args.password_file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timeout=args.timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(result)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&amp;amp;#32;if&amp;amp;#32;result[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;operation&amp;amp;#32;refused&amp;amp;#32;or&amp;amp;#32;failed;&amp;amp;#32;inspect&amp;amp;#32;configuration/event&amp;amp;#32;locally&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;background&amp;amp;#32;learning&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;bounded&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback&amp;amp;#32;queue.&lt;br /&gt;
&lt;br /&gt;
The&amp;amp;#32;collector&amp;amp;#32;remains&amp;amp;#32;a&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;writer.&amp;amp;#32;This&amp;amp;#32;process&amp;amp;#32;owns&amp;amp;#32;controller&lt;br /&gt;
credentials,&amp;amp;#32;learns&amp;amp;#32;only&amp;amp;#32;validated&amp;amp;#32;INBOX/Junk&amp;amp;#32;events,&amp;amp;#32;preserves&amp;amp;#32;every&amp;amp;#32;original,&lt;br /&gt;
and&amp;amp;#32;never&amp;amp;#32;retries&amp;amp;#32;a&amp;amp;#32;training&amp;amp;#32;operation&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result.&amp;amp;#32;Run&amp;amp;#32;foreground&lt;br /&gt;
under&amp;amp;#32;FreeBSD&amp;amp;#32;daemon(8),&amp;amp;#32;or&amp;amp;#32;use&amp;amp;#32;--once&amp;amp;#32;for&amp;amp;#32;one&amp;amp;#32;bounded&amp;amp;#32;batch.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;signal&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;threading&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
&lt;br /&gt;
_spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_worker_admin&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback-admin.py&amp;amp;quot;))&lt;br /&gt;
admin&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;admin.FeedbackError&lt;br /&gt;
SHA_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{64}\Z&amp;amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
UNCERTAIN&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;log_event(kind,&amp;amp;#32;**fields):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Callers&amp;amp;#32;pass&amp;amp;#32;only&amp;amp;#32;fixed&amp;amp;#32;diagnostics,&amp;amp;#32;numeric&amp;amp;#32;counts,&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;classes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps({&amp;amp;quot;event&amp;amp;quot;:&amp;amp;#32;kind,&amp;amp;#32;**fields},&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True),&amp;amp;#32;flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_timestamp_ns(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(value,&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;missing&amp;amp;#32;timestamp&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parsed&amp;amp;#32;=&amp;amp;#32;datetime.fromisoformat(value.replace(&amp;amp;quot;Z&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;+00:00&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;parsed.tzinfo&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;timestamp&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;timezone&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;int(parsed.timestamp()&amp;amp;#32;*&amp;amp;#32;1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;order_events(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;bounded&amp;amp;#32;metadata,&amp;amp;#32;ordered&amp;amp;#32;by&amp;amp;#32;collector&amp;amp;#32;created_ns,&amp;amp;#32;then&amp;amp;#32;event&amp;amp;#32;ID.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;Legacy&amp;amp;#32;second-resolution&amp;amp;#32;timestamps&amp;amp;#32;are&amp;amp;#32;permitted&amp;amp;#32;only&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;same&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;contradictory&amp;amp;#32;labels&amp;amp;#32;at&amp;amp;#32;an&amp;amp;#32;identical&amp;amp;#32;timestamp.&amp;amp;#32;Such&amp;amp;#32;a&amp;amp;#32;tie&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cannot&amp;amp;#32;be&amp;amp;#32;resolved&amp;amp;#32;from&amp;amp;#32;random&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;is&amp;amp;#32;quarantined&amp;amp;#32;without&amp;amp;#32;learning.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pending&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows&amp;amp;#32;=&amp;amp;#32;[]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(pending)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;queue&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending&amp;amp;#32;/&amp;amp;#32;entry.name)&amp;amp;#32;as&amp;amp;#32;directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(directory.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;dict(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;sha256&amp;amp;quot;),&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(metadata[&amp;amp;quot;sha256&amp;amp;quot;]):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timestamp&amp;amp;#32;=&amp;amp;#32;metadata.get(&amp;amp;quot;created_ns&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;int)&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;bool)&amp;amp;#32;and&amp;amp;#32;timestamp&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;timestamp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;metadata:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(metadata.get(&amp;amp;quot;created_utc&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;manual&amp;amp;#32;administrator&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;archived&amp;amp;#32;this&amp;amp;#32;candidate.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;_invalid&amp;amp;quot;:&amp;amp;#32;True,&amp;amp;#32;&amp;amp;quot;_order_ns&amp;amp;quot;:&amp;amp;#32;0})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied&amp;amp;#32;=&amp;amp;#32;{}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied.setdefault((row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;]),&amp;amp;#32;set()).add(row[&amp;amp;quot;label&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;)&amp;amp;#32;and&amp;amp;#32;len(tied[(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;])])&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;sorted(rows,&amp;amp;#32;key=lambda&amp;amp;#32;row:&amp;amp;#32;(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;id&amp;amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_journal(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;root-owned&amp;amp;#32;receipts;&amp;amp;#32;successful&amp;amp;#32;classes&amp;amp;#32;use&amp;amp;#32;durable&amp;amp;#32;worker&amp;amp;#32;sequence.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked&amp;amp;#32;=&amp;amp;#32;{},&amp;amp;#32;set()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;used_bytes&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(processed)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;MAX_JOURNAL_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;processed&amp;amp;#32;/&amp;amp;#32;entry.name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.receipt_for(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(receipt,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;receipt.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;receipt&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(digest,&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(digest):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;worker_sequence&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(sequence,&amp;amp;#32;int)&amp;amp;#32;or&amp;amp;#32;isinstance(sequence,&amp;amp;#32;bool)&amp;amp;#32;or&amp;amp;#32;sequence&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;sequence&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;max(max_sequence,&amp;amp;#32;sequence)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;in&amp;amp;#32;UNCERTAIN:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;selected_class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;learned&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Existing&amp;amp;#32;manual&amp;amp;#32;receipts&amp;amp;#32;predate&amp;amp;#32;automatic&amp;amp;#32;processing;&amp;amp;#32;the&amp;amp;#32;old&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;admin&amp;amp;#32;refuses&amp;amp;#32;further&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;a&amp;amp;#32;SHA&amp;amp;#32;once&amp;amp;#32;it&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;receipt.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fallback&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(receipt.get(&amp;amp;quot;finished_utc&amp;amp;quot;,&amp;amp;#32;receipt.get(&amp;amp;quot;reviewed_utc&amp;amp;quot;)))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;position&amp;amp;#32;=&amp;amp;#32;(bool(sequence),&amp;amp;#32;sequence&amp;amp;#32;if&amp;amp;#32;sequence&amp;amp;#32;else&amp;amp;#32;fallback,&amp;amp;#32;entry.name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;or&amp;amp;#32;position&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;latest[digest][0]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;(position,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Count&amp;amp;#32;snapshots&amp;amp;#32;and&amp;amp;#32;preserved&amp;amp;#32;originals,&amp;amp;#32;never&amp;amp;#32;recursively&amp;amp;#32;walk&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;arbitrary&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail-user-owned&amp;amp;#32;source/&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;part&amp;amp;#32;in&amp;amp;#32;(directory,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(part)&amp;amp;#32;as&amp;amp;#32;safe:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;safe.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.stat(name,&amp;amp;#32;dir_fd=safe.fd,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(part&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_check_trusted_parent_chain(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;parent&amp;amp;#32;in&amp;amp;#32;Path(path).parents:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;link_info&amp;amp;#32;=&amp;amp;#32;parent.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;parent.stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(link_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;or&amp;amp;#32;info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;executable&amp;amp;#32;parent&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;controlled&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;resolve_executable(executable):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Allow&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;symlinks,&amp;amp;#32;but&amp;amp;#32;pin&amp;amp;#32;both&amp;amp;#32;operations&amp;amp;#32;to&amp;amp;#32;their&amp;amp;#32;target.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;source.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source_info&amp;amp;#32;=&amp;amp;#32;source.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;source_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;entry&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(source)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target&amp;amp;#32;=&amp;amp;#32;source.resolve(strict=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;target.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(target)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;controller_ready(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Authenticated&amp;amp;#32;read-only&amp;amp;#32;controller&amp;amp;#32;probe;&amp;amp;#32;no&amp;amp;#32;candidate&amp;amp;#32;is&amp;amp;#32;archived&amp;amp;#32;yet.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[resolve_executable(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unsafe&amp;amp;#32;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;stat&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;stdin=subprocess.DEVNULL,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;check=False,&amp;amp;#32;timeout=min(timeout,&amp;amp;#32;5)).returncode&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;process_pending(root,&amp;amp;#32;*,&amp;amp;#32;executable=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;,&amp;amp;#32;controller=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=admin.DEFAULT_PASSWORD_FILE,&amp;amp;#32;timeout=30,&amp;amp;#32;batch_size=20,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight=None,&amp;amp;#32;learner=None,&amp;amp;#32;stop_event=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Process&amp;amp;#32;at&amp;amp;#32;most&amp;amp;#32;batch_size&amp;amp;#32;candidates.&amp;amp;#32;Inject&amp;amp;#32;probes/learner&amp;amp;#32;for&amp;amp;#32;unit&amp;amp;#32;tests.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;One&amp;amp;#32;admin&amp;amp;#32;lock&amp;amp;#32;serializes&amp;amp;#32;manual&amp;amp;#32;and&amp;amp;#32;background&amp;amp;#32;operations.&amp;amp;#32;A&amp;amp;#32;crash&amp;amp;#32;after&amp;amp;#32;a&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;durable&amp;amp;#32;learning&amp;amp;#32;intent&amp;amp;#32;leaves&amp;amp;#32;status=learning,&amp;amp;#32;which&amp;amp;#32;blocks&amp;amp;#32;this&amp;amp;#32;SHA&amp;amp;#32;on&amp;amp;#32;the&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;next&amp;amp;#32;pass;&amp;amp;#32;subsequent&amp;amp;#32;candidates&amp;amp;#32;are&amp;amp;#32;preserved&amp;amp;#32;as&amp;amp;#32;quarantined_uncertain.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight&amp;amp;#32;=&amp;amp;#32;controller_ready&amp;amp;#32;if&amp;amp;#32;preflight&amp;amp;#32;is&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;preflight&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;learner&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;pkg&amp;amp;#32;installs&amp;amp;#32;rspamc&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;root-owned&amp;amp;#32;versioned&amp;amp;#32;symlink.&amp;amp;#32;Resolve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;it&amp;amp;#32;once&amp;amp;#32;so&amp;amp;#32;preflight&amp;amp;#32;and&amp;amp;#32;training&amp;amp;#32;invoke&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;trusted&amp;amp;#32;executable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;resolve_executable(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learner&amp;amp;#32;=&amp;amp;#32;admin.run_learning&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;learned&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;quot;:&amp;amp;#32;0,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;controller_unavailable&amp;amp;quot;:&amp;amp;#32;False,&amp;amp;#32;&amp;amp;quot;storage_blocked&amp;amp;quot;:&amp;amp;#32;False,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;archive_warning&amp;amp;quot;:&amp;amp;#32;False}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;events&amp;amp;#32;=&amp;amp;#32;order_events(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;len(events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;archive_count&amp;amp;#32;=&amp;amp;#32;_journal(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;preflight(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;candidate&amp;amp;#32;in&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;handled&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;batch_size&amp;amp;#32;or&amp;amp;#32;(stop_event&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;and&amp;amp;#32;stop_event.is_set()):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;candidate[&amp;amp;quot;id&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id).exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Crash&amp;amp;#32;before&amp;amp;#32;prepare_archive&amp;amp;#32;moved&amp;amp;#32;pending/ID:&amp;amp;#32;its&amp;amp;#32;root-owned&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;intent&amp;amp;#32;is&amp;amp;#32;authoritative.&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;pending&amp;amp;#32;original&amp;amp;#32;for&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;manual&amp;amp;#32;recovery&amp;amp;#32;and&amp;amp;#32;continue&amp;amp;#32;independent&amp;amp;#32;content,&amp;amp;#32;never&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;admin.read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;act&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;queued&amp;amp;#32;ordering/identity&amp;amp;#32;changed&amp;amp;#32;since&amp;amp;#32;enumeration.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;any(metadata.get(key)&amp;amp;#32;!=&amp;amp;#32;candidate.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(&amp;amp;quot;sha256&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;+&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label,&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;metadata[&amp;amp;quot;label&amp;amp;quot;],&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_ordering_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;event&amp;amp;#32;ordering&amp;amp;#32;is&amp;amp;#32;ambiguous&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;blocked:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;a&amp;amp;#32;prior&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;#32;has&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;and&amp;amp;#32;latest[digest][1]&amp;amp;#32;==&amp;amp;#32;label:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;same&amp;amp;#32;class&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;latest&amp;amp;#32;successful&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;automatic&amp;amp;#32;explicit&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label,&amp;amp;#32;reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;prepare_archive&amp;amp;#32;writes&amp;amp;#32;a&amp;amp;#32;root&amp;amp;#32;journal&amp;amp;#32;before&amp;amp;#32;moving&amp;amp;#32;the&amp;amp;#32;source;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;stop&amp;amp;#32;instead&amp;amp;#32;of&amp;amp;#32;retrying&amp;amp;#32;an&amp;amp;#32;incompletely&amp;amp;#32;prepared&amp;amp;#32;operation.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt.update({&amp;amp;quot;mode&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;automatic_imap_feedback&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;worker_sequence&amp;amp;quot;:&amp;amp;#32;sequence,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;processed_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&amp;amp;#32;&amp;amp;quot;source_created_ns&amp;amp;quot;:&amp;amp;#32;candidate[&amp;amp;quot;_order_ns&amp;amp;quot;]})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;previous_successful_class&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;latest[digest][1]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Durable&amp;amp;#32;intent&amp;amp;#32;exists&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;call&amp;amp;#32;that&amp;amp;#32;may&amp;amp;#32;change&amp;amp;#32;Bayes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;returncode&amp;amp;#32;=&amp;amp;#32;learner(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;failure;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;returncode&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;admin.collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;((True,&amp;amp;#32;sequence,&amp;amp;#32;event_id),&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;learned&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;duplicates&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive_count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;-=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;feedback_processed&amp;amp;quot;,&amp;amp;#32;id=event_id,&amp;amp;#32;label=label,&amp;amp;#32;status=receipt[&amp;amp;quot;status&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;controller&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;failed&amp;amp;#32;after&amp;amp;#32;the&amp;amp;#32;preflight.&amp;amp;#32;Leave&amp;amp;#32;all&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;other&amp;amp;#32;candidates&amp;amp;#32;pending&amp;amp;#32;until&amp;amp;#32;a&amp;amp;#32;fresh&amp;amp;#32;read-only&amp;amp;#32;probe&amp;amp;#32;passes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--poll&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--batch-size&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=20)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--once&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0.2&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.poll&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;60&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.batch_size&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;100:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;worker&amp;amp;#32;options&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping&amp;amp;#32;=&amp;amp;#32;threading.Event()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;signum&amp;amp;#32;in&amp;amp;#32;(signal.SIGTERM,&amp;amp;#32;signal.SIGINT):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;signal.signal(signum,&amp;amp;#32;lambda&amp;amp;#32;*_:&amp;amp;#32;stopping.set())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;lockfd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.worker.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(lockfd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;not&amp;amp;#32;stopping.is_set():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;process_pending(root,&amp;amp;#32;executable=args.rspamc_bin,&amp;amp;#32;controller=args.controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=args.password_file,&amp;amp;#32;timeout=args.timeout,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;batch_size=args.batch_size,&amp;amp;#32;stop_event=stopping)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;state&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;value&amp;amp;#32;for&amp;amp;#32;key,&amp;amp;#32;value&amp;amp;#32;in&amp;amp;#32;summary.items()&amp;amp;#32;if&amp;amp;#32;key&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;!=&amp;amp;#32;previous_state&amp;amp;#32;or&amp;amp;#32;any(summary[key]&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_state&amp;amp;quot;,&amp;amp;#32;**summary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;state&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;2&amp;amp;#32;if&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;or&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;else&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;previous_state&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_error&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;local&amp;amp;#32;queue,&amp;amp;#32;journal&amp;amp;#32;or&amp;amp;#32;configuration&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;#32;of&amp;amp;#32;uncertain&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping.wait(args.poll)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_start_failed&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;root,&amp;amp;#32;configuration&amp;amp;#32;or&amp;amp;#32;worker&amp;amp;#32;lock&amp;amp;#32;required&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(1)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=678</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=678"/>
		<updated>2026-10-29T23:15:27Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 2. Проверить систему без изменений */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;25&amp;amp;#32;/&amp;amp;#32;STARTTLS&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;465&amp;amp;#32;TLS&amp;amp;#32;или&amp;amp;#32;587&amp;amp;#32;STARTTLS&amp;amp;#32;+&amp;amp;#32;AUTH&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;LMTP&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;/opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;AUTH&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;файловый&amp;amp;#32;каталог&amp;amp;#32;пользователей&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;993&amp;amp;#32;TLS&amp;amp;#32;→&amp;amp;#32;Dovecot&lt;br /&gt;
SnappyMail&amp;amp;#32;(необязательно)&amp;amp;#32;→&amp;amp;#32;те&amp;amp;#32;же&amp;amp;#32;IMAPS&amp;amp;#32;и&amp;amp;#32;SMTP&amp;amp;#32;AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version -kru&lt;br /&gt;
uname -m&lt;br /&gt;
hostname -f&lt;br /&gt;
ifconfig -a&lt;br /&gt;
netstat -rn&lt;br /&gt;
cat /etc/resolv.conf&lt;br /&gt;
mount -p&lt;br /&gt;
df -hT&lt;br /&gt;
swapinfo -h&lt;br /&gt;
geom disk list da1&lt;br /&gt;
gpart show -p&lt;br /&gt;
gpart show -p da1&lt;br /&gt;
glabel status&lt;br /&gt;
fstyp /dev/da1&lt;br /&gt;
file -s /dev/da1&lt;br /&gt;
sockstat -46l&lt;br /&gt;
ls -ld /opt&lt;br /&gt;
cat /etc/fstab&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sysrc&amp;amp;#32;hostname=mail.example.org&lt;br /&gt;
hostname&amp;amp;#32;mail.example.org&lt;br /&gt;
sysrc&amp;amp;#32;defaultrouter=198.51.100.1&lt;br /&gt;
#&amp;amp;#32;Только&amp;amp;#32;если&amp;amp;#32;в&amp;amp;#32;работающей&amp;amp;#32;таблице&amp;amp;#32;отсутствует&amp;amp;#32;default&amp;amp;#32;route:&lt;br /&gt;
route&amp;amp;#32;add&amp;amp;#32;default&amp;amp;#32;198.51.100.1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo&amp;amp;#32;/dev/da1&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;{print&amp;amp;#32;$3}&#039;)&lt;br /&gt;
test&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;-gt&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;/dev/da1&amp;amp;#32;/dev/zero&lt;br /&gt;
printf&amp;amp;#32;&#039;cmp&amp;amp;#32;status:&amp;amp;#32;%s\n&#039;&amp;amp;#32;&amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;update&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
#&amp;amp;#32;После&amp;amp;#32;проверки&amp;amp;#32;плана:&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
sysrc&amp;amp;#32;exim_enable=NO&amp;amp;#32;dovecot_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_enable=NONE&amp;amp;#32;sendmail_submit_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_outbound_enable=NO&amp;amp;#32;sendmail_msp_queue_enable=NO&lt;br /&gt;
service&amp;amp;#32;sendmail&amp;amp;#32;onestop&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/dovecot&amp;amp;#32;--version&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;mailnull&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;mail&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;review&amp;amp;#32;and&amp;amp;#32;explicit&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback.&lt;br /&gt;
&lt;br /&gt;
No&amp;amp;#32;command&amp;amp;#32;trains&amp;amp;#32;automatically.&amp;amp;#32;list/show&amp;amp;#32;are&amp;amp;#32;local&amp;amp;#32;reads.&amp;amp;#32;learn&amp;amp;#32;ID&amp;amp;#32;spam|ham&lt;br /&gt;
is&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;command&amp;amp;#32;that&amp;amp;#32;invokes&amp;amp;#32;rspamc.&amp;amp;#32;reject&amp;amp;#32;ID&amp;amp;#32;archives&amp;amp;#32;without&amp;amp;#32;training.&lt;br /&gt;
Queue&amp;amp;#32;root&amp;amp;#32;and&amp;amp;#32;processed&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;non-writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users;&lt;br /&gt;
pending&amp;amp;#32;is&amp;amp;#32;separately&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;vmail,&amp;amp;#32;mode&amp;amp;#32;0700.&amp;amp;#32;Archived&amp;amp;#32;originals&amp;amp;#32;remain&amp;amp;#32;in&lt;br /&gt;
processed/ID/source;&amp;amp;#32;only&amp;amp;#32;root-owned&amp;amp;#32;snapshots&amp;amp;#32;are&amp;amp;#32;used&amp;amp;#32;for&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;review.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;email&amp;amp;#32;import&amp;amp;#32;policy&lt;br /&gt;
from&amp;amp;#32;email.parser&amp;amp;#32;import&amp;amp;#32;BytesHeaderParser&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
&lt;br /&gt;
spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_collector&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback.py&amp;amp;quot;))&lt;br /&gt;
collector&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;collector.FeedbackError&lt;br /&gt;
ID_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{32}\Z&amp;amp;quot;)&lt;br /&gt;
DEFAULT_ROOT&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback&amp;amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/usr/local/etc/rspamd/controller.password&amp;amp;quot;&lt;br /&gt;
HEADERS&amp;amp;#32;=&amp;amp;#32;(&amp;amp;quot;From&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;To&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Date&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Subject&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Message-ID&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Status&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Score&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Rspamd-Action&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;emit(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;require_root():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;hasattr(os,&amp;amp;#32;&amp;amp;quot;geteuid&amp;amp;quot;)&amp;amp;#32;or&amp;amp;#32;os.geteuid()&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;administrative&amp;amp;#32;command&amp;amp;#32;requires&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_id(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;ID&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;exactly&amp;amp;#32;32&amp;amp;#32;lowercase&amp;amp;#32;hexadecimal&amp;amp;#32;characters&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;Directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Nofollow&amp;amp;#32;directory&amp;amp;#32;descriptor&amp;amp;#32;on&amp;amp;#32;FreeBSD;&amp;amp;#32;portable&amp;amp;#32;fallback&amp;amp;#32;for&amp;amp;#32;local&amp;amp;#32;tests.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__init__(self,&amp;amp;#32;path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;self.path.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;expected&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;os.open(self.path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__enter__(self):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;self&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__exit__(self,&amp;amp;#32;*args):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;read(self,&amp;amp;#32;name,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;&amp;amp;quot;\\&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;.&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;..&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;internal&amp;amp;#32;filename&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(name,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW&amp;amp;#32;|&amp;amp;#32;os.O_NONBLOCK,&amp;amp;#32;dir_fd=self.fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;self.path&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;symlink&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;event&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;rb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;source:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.fstat(source.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;info.st_nlink&amp;amp;#32;!=&amp;amp;#32;1&amp;amp;#32;or&amp;amp;#32;info.st_size&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;bounded,&amp;amp;#32;single-link&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;source.read(limit&amp;amp;#32;+&amp;amp;#32;1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(result)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;exceeded&amp;amp;#32;the&amp;amp;#32;size&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;read_event(directory,&amp;amp;#32;event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;On&amp;amp;#32;POSIX&amp;amp;#32;open&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;relative&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;already&amp;amp;#32;opened&amp;amp;#32;parent&amp;amp;#32;descriptor.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;parent&amp;amp;#32;path&amp;amp;#32;is&amp;amp;#32;deployment-owned,&amp;amp;#32;and&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;event&amp;amp;#32;is&amp;amp;#32;rejected.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(Path(directory)&amp;amp;#32;/&amp;amp;#32;event_id)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;entry.read(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;MAX_MESSAGE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;user&amp;amp;quot;),&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;event_id&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;bytes&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;len(message)&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;hashlib.sha256(message).hexdigest():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;content&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;its&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata,&amp;amp;#32;message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write_private(path,&amp;amp;#32;value,&amp;amp;#32;*,&amp;amp;#32;json_value=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;flags,&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;json_value:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;value&amp;amp;#32;=&amp;amp;#32;(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;\n&amp;amp;quot;).encode()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(value)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;update_receipt(directory,&amp;amp;#32;receipt):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin-new.json&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(temporary,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.replace(temporary,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_root_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;Path(path).stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;administrative&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;group/other&amp;amp;#32;writable&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;receipt_for(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;find_prior(root,&amp;amp;#32;sha256):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;damaged&amp;amp;#32;journal&amp;amp;#32;must&amp;amp;#32;never&amp;amp;#32;silently&amp;amp;#32;allow&amp;amp;#32;repeated&amp;amp;#32;training.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;is&amp;amp;#32;incomplete;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;before&amp;amp;#32;more&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;==&amp;amp;#32;sha256&amp;amp;#32;and&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label=None,&amp;amp;#32;reason=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(root).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;collector.MIN_FREE_BYTES&amp;amp;#32;+&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;#32;to&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;reviewed&amp;amp;#32;snapshot&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;],&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;reviewed_utc&amp;amp;quot;:&amp;amp;#32;collector.utc_now(),&amp;amp;#32;&amp;amp;quot;status&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;preparing&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;selected_class&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;reason&amp;amp;quot;:&amp;amp;#32;reason}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;json.dumps(metadata,&amp;amp;#32;ensure_ascii=True).encode())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;complete&amp;amp;#32;original&amp;amp;#32;event.&amp;amp;#32;Never&amp;amp;#32;traverse&amp;amp;#32;or&amp;amp;#32;execute&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;source/.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id,&amp;amp;#32;archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;status&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;archive,&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;executable.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;path&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[str(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;rspamc&amp;amp;#32;reads&amp;amp;#32;the&amp;amp;#32;file&amp;amp;#32;itself;&amp;amp;#32;the&amp;amp;#32;secret&amp;amp;#32;never&amp;amp;#32;appears&amp;amp;#32;in&amp;amp;#32;argv&amp;amp;#32;or&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;learn_&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;-u/-d:&amp;amp;#32;one&amp;amp;#32;shared&amp;amp;#32;Bayes&amp;amp;#32;classifier.&amp;amp;#32;No&amp;amp;#32;shell&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;input=message,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;timeout=timeout,&amp;amp;#32;check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;learn_event(root,&amp;amp;#32;event_id,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;training&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;prior&amp;amp;#32;=&amp;amp;#32;find_prior(root,&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prior:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;prior&amp;amp;#32;learned&amp;amp;#32;or&amp;amp;#32;uncertain&amp;amp;#32;event;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;and&amp;amp;#32;reject&amp;amp;#32;the&amp;amp;#32;duplicate&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rc&amp;amp;#32;=&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;subprocess.TimeoutExpired:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;timeout;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;automatically&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_failed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;could&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;or&amp;amp;#32;local&amp;amp;#32;configuration&amp;amp;#32;was&amp;amp;#32;invalid&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;rc&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;rc&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;rc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;inspect&amp;amp;#32;server&amp;amp;#32;before&amp;amp;#32;any&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;reject_event(root,&amp;amp;#32;event_id,&amp;amp;#32;reason=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(reason)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;500:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;reason&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;rejected&amp;amp;quot;,&amp;amp;#32;reason=reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;list_events(root,&amp;amp;#32;state,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;((&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;(state,)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;directory.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;directory.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;notice&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;limit&amp;amp;#32;reached&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;limit&amp;amp;quot;:&amp;amp;#32;limit})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(entry)&amp;amp;#32;as&amp;amp;#32;item:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(item.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;metadata.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;id&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;state&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;review&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(row)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;state&amp;amp;quot;:&amp;amp;#32;name,&amp;amp;#32;&amp;amp;quot;error&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;#32;event;&amp;amp;#32;manual&amp;amp;#32;inspection&amp;amp;#32;required&amp;amp;quot;})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;show_event(root,&amp;amp;#32;event_id,&amp;amp;#32;state,&amp;amp;#32;path_only):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;state,&amp;amp;#32;check_id(event_id))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path_only:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;message_path&amp;amp;quot;:&amp;amp;#32;str(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;)})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;headers&amp;amp;#32;=&amp;amp;#32;BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message),&amp;amp;#32;262144)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;metadata&amp;amp;quot;:&amp;amp;#32;metadata,&amp;amp;#32;&amp;amp;quot;headers&amp;amp;quot;:&amp;amp;#32;{name:&amp;amp;#32;[str(v)[:2000]&amp;amp;#32;for&amp;amp;#32;v&amp;amp;#32;in&amp;amp;#32;headers.get_all(name,&amp;amp;#32;[])[:10]]&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;HEADERS}})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;review&amp;amp;quot;:&amp;amp;#32;receipt_for(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;warn_processed_storage(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;event&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event.name)&amp;amp;#32;or&amp;amp;#32;event.is_symlink()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;event.is_dir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;prefix&amp;amp;#32;in&amp;amp;#32;(event,&amp;amp;#32;event&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prefix.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(prefix&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_BYTES&amp;amp;#32;or&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;WARNING&amp;amp;#32;processed&amp;amp;#32;archive&amp;amp;#32;reached&amp;amp;#32;512&amp;amp;#32;MiB&amp;amp;#32;or&amp;amp;#32;1024&amp;amp;#32;events;&amp;amp;#32;review&amp;amp;#32;retention&amp;amp;#32;manually;&amp;amp;#32;nothing&amp;amp;#32;deleted&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sub&amp;amp;#32;=&amp;amp;#32;parser.add_subparsers(dest=&amp;amp;quot;command&amp;amp;quot;,&amp;amp;#32;required=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;list&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;JSON&amp;amp;#32;lines,&amp;amp;#32;metadata&amp;amp;#32;only&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--limit&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=100)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;show&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;selected&amp;amp;#32;headers&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;dumps&amp;amp;#32;the&amp;amp;#32;body&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--path&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;learn&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;explicitly&amp;amp;#32;train&amp;amp;#32;one&amp;amp;#32;reviewed&amp;amp;#32;event,&amp;amp;#32;then&amp;amp;#32;archive&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;help=&amp;amp;quot;root-only&amp;amp;#32;plaintext&amp;amp;#32;secret&amp;amp;#32;file;&amp;amp;#32;use&amp;amp;#32;--no-password&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;an&amp;amp;#32;intentionally&amp;amp;#32;trusted&amp;amp;#32;local&amp;amp;#32;controller&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--no-password&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;reject&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;archive&amp;amp;#32;one&amp;amp;#32;event&amp;amp;#32;without&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;deleting&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;--reason&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;root&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn_processed_storage(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;list&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.limit&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;10000:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;limit&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;1&amp;amp;#32;and&amp;amp;#32;10000&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;list_events(root,&amp;amp;#32;args.state,&amp;amp;#32;args.limit)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;show&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;show_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.state,&amp;amp;#32;args.path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;reject&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(reject_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.reason))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;timeout&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;0&amp;amp;#32;and&amp;amp;#32;300&amp;amp;#32;seconds&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;learn_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.label,&amp;amp;#32;executable=args.rspamc_bin,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;controller=args.controller,&amp;amp;#32;password_file=None&amp;amp;#32;if&amp;amp;#32;args.no_password&amp;amp;#32;else&amp;amp;#32;args.password_file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timeout=args.timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(result)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&amp;amp;#32;if&amp;amp;#32;result[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;operation&amp;amp;#32;refused&amp;amp;#32;or&amp;amp;#32;failed;&amp;amp;#32;inspect&amp;amp;#32;configuration/event&amp;amp;#32;locally&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;background&amp;amp;#32;learning&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;bounded&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback&amp;amp;#32;queue.&lt;br /&gt;
&lt;br /&gt;
The&amp;amp;#32;collector&amp;amp;#32;remains&amp;amp;#32;a&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;writer.&amp;amp;#32;This&amp;amp;#32;process&amp;amp;#32;owns&amp;amp;#32;controller&lt;br /&gt;
credentials,&amp;amp;#32;learns&amp;amp;#32;only&amp;amp;#32;validated&amp;amp;#32;INBOX/Junk&amp;amp;#32;events,&amp;amp;#32;preserves&amp;amp;#32;every&amp;amp;#32;original,&lt;br /&gt;
and&amp;amp;#32;never&amp;amp;#32;retries&amp;amp;#32;a&amp;amp;#32;training&amp;amp;#32;operation&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result.&amp;amp;#32;Run&amp;amp;#32;foreground&lt;br /&gt;
under&amp;amp;#32;FreeBSD&amp;amp;#32;daemon(8),&amp;amp;#32;or&amp;amp;#32;use&amp;amp;#32;--once&amp;amp;#32;for&amp;amp;#32;one&amp;amp;#32;bounded&amp;amp;#32;batch.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;signal&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;threading&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
&lt;br /&gt;
_spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_worker_admin&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback-admin.py&amp;amp;quot;))&lt;br /&gt;
admin&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;admin.FeedbackError&lt;br /&gt;
SHA_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{64}\Z&amp;amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
UNCERTAIN&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;log_event(kind,&amp;amp;#32;**fields):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Callers&amp;amp;#32;pass&amp;amp;#32;only&amp;amp;#32;fixed&amp;amp;#32;diagnostics,&amp;amp;#32;numeric&amp;amp;#32;counts,&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;classes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps({&amp;amp;quot;event&amp;amp;quot;:&amp;amp;#32;kind,&amp;amp;#32;**fields},&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True),&amp;amp;#32;flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_timestamp_ns(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(value,&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;missing&amp;amp;#32;timestamp&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parsed&amp;amp;#32;=&amp;amp;#32;datetime.fromisoformat(value.replace(&amp;amp;quot;Z&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;+00:00&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;parsed.tzinfo&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;timestamp&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;timezone&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;int(parsed.timestamp()&amp;amp;#32;*&amp;amp;#32;1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;order_events(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;bounded&amp;amp;#32;metadata,&amp;amp;#32;ordered&amp;amp;#32;by&amp;amp;#32;collector&amp;amp;#32;created_ns,&amp;amp;#32;then&amp;amp;#32;event&amp;amp;#32;ID.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;Legacy&amp;amp;#32;second-resolution&amp;amp;#32;timestamps&amp;amp;#32;are&amp;amp;#32;permitted&amp;amp;#32;only&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;same&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;contradictory&amp;amp;#32;labels&amp;amp;#32;at&amp;amp;#32;an&amp;amp;#32;identical&amp;amp;#32;timestamp.&amp;amp;#32;Such&amp;amp;#32;a&amp;amp;#32;tie&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cannot&amp;amp;#32;be&amp;amp;#32;resolved&amp;amp;#32;from&amp;amp;#32;random&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;is&amp;amp;#32;quarantined&amp;amp;#32;without&amp;amp;#32;learning.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pending&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows&amp;amp;#32;=&amp;amp;#32;[]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(pending)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;queue&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending&amp;amp;#32;/&amp;amp;#32;entry.name)&amp;amp;#32;as&amp;amp;#32;directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(directory.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;dict(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;sha256&amp;amp;quot;),&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(metadata[&amp;amp;quot;sha256&amp;amp;quot;]):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timestamp&amp;amp;#32;=&amp;amp;#32;metadata.get(&amp;amp;quot;created_ns&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;int)&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;bool)&amp;amp;#32;and&amp;amp;#32;timestamp&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;timestamp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;metadata:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(metadata.get(&amp;amp;quot;created_utc&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;manual&amp;amp;#32;administrator&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;archived&amp;amp;#32;this&amp;amp;#32;candidate.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;_invalid&amp;amp;quot;:&amp;amp;#32;True,&amp;amp;#32;&amp;amp;quot;_order_ns&amp;amp;quot;:&amp;amp;#32;0})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied&amp;amp;#32;=&amp;amp;#32;{}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied.setdefault((row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;]),&amp;amp;#32;set()).add(row[&amp;amp;quot;label&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;)&amp;amp;#32;and&amp;amp;#32;len(tied[(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;])])&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;sorted(rows,&amp;amp;#32;key=lambda&amp;amp;#32;row:&amp;amp;#32;(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;id&amp;amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_journal(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;root-owned&amp;amp;#32;receipts;&amp;amp;#32;successful&amp;amp;#32;classes&amp;amp;#32;use&amp;amp;#32;durable&amp;amp;#32;worker&amp;amp;#32;sequence.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked&amp;amp;#32;=&amp;amp;#32;{},&amp;amp;#32;set()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;used_bytes&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(processed)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;MAX_JOURNAL_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;processed&amp;amp;#32;/&amp;amp;#32;entry.name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.receipt_for(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(receipt,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;receipt.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;receipt&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(digest,&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(digest):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;worker_sequence&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(sequence,&amp;amp;#32;int)&amp;amp;#32;or&amp;amp;#32;isinstance(sequence,&amp;amp;#32;bool)&amp;amp;#32;or&amp;amp;#32;sequence&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;sequence&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;max(max_sequence,&amp;amp;#32;sequence)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;in&amp;amp;#32;UNCERTAIN:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;selected_class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;learned&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Existing&amp;amp;#32;manual&amp;amp;#32;receipts&amp;amp;#32;predate&amp;amp;#32;automatic&amp;amp;#32;processing;&amp;amp;#32;the&amp;amp;#32;old&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;admin&amp;amp;#32;refuses&amp;amp;#32;further&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;a&amp;amp;#32;SHA&amp;amp;#32;once&amp;amp;#32;it&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;receipt.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fallback&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(receipt.get(&amp;amp;quot;finished_utc&amp;amp;quot;,&amp;amp;#32;receipt.get(&amp;amp;quot;reviewed_utc&amp;amp;quot;)))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;position&amp;amp;#32;=&amp;amp;#32;(bool(sequence),&amp;amp;#32;sequence&amp;amp;#32;if&amp;amp;#32;sequence&amp;amp;#32;else&amp;amp;#32;fallback,&amp;amp;#32;entry.name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;or&amp;amp;#32;position&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;latest[digest][0]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;(position,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Count&amp;amp;#32;snapshots&amp;amp;#32;and&amp;amp;#32;preserved&amp;amp;#32;originals,&amp;amp;#32;never&amp;amp;#32;recursively&amp;amp;#32;walk&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;arbitrary&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail-user-owned&amp;amp;#32;source/&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;part&amp;amp;#32;in&amp;amp;#32;(directory,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(part)&amp;amp;#32;as&amp;amp;#32;safe:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;safe.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.stat(name,&amp;amp;#32;dir_fd=safe.fd,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(part&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_check_trusted_parent_chain(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;parent&amp;amp;#32;in&amp;amp;#32;Path(path).parents:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;link_info&amp;amp;#32;=&amp;amp;#32;parent.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;parent.stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(link_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;or&amp;amp;#32;info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;executable&amp;amp;#32;parent&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;controlled&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;resolve_executable(executable):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Allow&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;symlinks,&amp;amp;#32;but&amp;amp;#32;pin&amp;amp;#32;both&amp;amp;#32;operations&amp;amp;#32;to&amp;amp;#32;their&amp;amp;#32;target.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;source.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source_info&amp;amp;#32;=&amp;amp;#32;source.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;source_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;entry&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(source)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target&amp;amp;#32;=&amp;amp;#32;source.resolve(strict=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;target.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(target)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;controller_ready(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Authenticated&amp;amp;#32;read-only&amp;amp;#32;controller&amp;amp;#32;probe;&amp;amp;#32;no&amp;amp;#32;candidate&amp;amp;#32;is&amp;amp;#32;archived&amp;amp;#32;yet.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[resolve_executable(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unsafe&amp;amp;#32;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;stat&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;stdin=subprocess.DEVNULL,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;check=False,&amp;amp;#32;timeout=min(timeout,&amp;amp;#32;5)).returncode&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;process_pending(root,&amp;amp;#32;*,&amp;amp;#32;executable=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;,&amp;amp;#32;controller=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=admin.DEFAULT_PASSWORD_FILE,&amp;amp;#32;timeout=30,&amp;amp;#32;batch_size=20,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight=None,&amp;amp;#32;learner=None,&amp;amp;#32;stop_event=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Process&amp;amp;#32;at&amp;amp;#32;most&amp;amp;#32;batch_size&amp;amp;#32;candidates.&amp;amp;#32;Inject&amp;amp;#32;probes/learner&amp;amp;#32;for&amp;amp;#32;unit&amp;amp;#32;tests.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;One&amp;amp;#32;admin&amp;amp;#32;lock&amp;amp;#32;serializes&amp;amp;#32;manual&amp;amp;#32;and&amp;amp;#32;background&amp;amp;#32;operations.&amp;amp;#32;A&amp;amp;#32;crash&amp;amp;#32;after&amp;amp;#32;a&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;durable&amp;amp;#32;learning&amp;amp;#32;intent&amp;amp;#32;leaves&amp;amp;#32;status=learning,&amp;amp;#32;which&amp;amp;#32;blocks&amp;amp;#32;this&amp;amp;#32;SHA&amp;amp;#32;on&amp;amp;#32;the&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;next&amp;amp;#32;pass;&amp;amp;#32;subsequent&amp;amp;#32;candidates&amp;amp;#32;are&amp;amp;#32;preserved&amp;amp;#32;as&amp;amp;#32;quarantined_uncertain.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight&amp;amp;#32;=&amp;amp;#32;controller_ready&amp;amp;#32;if&amp;amp;#32;preflight&amp;amp;#32;is&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;preflight&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;learner&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;pkg&amp;amp;#32;installs&amp;amp;#32;rspamc&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;root-owned&amp;amp;#32;versioned&amp;amp;#32;symlink.&amp;amp;#32;Resolve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;it&amp;amp;#32;once&amp;amp;#32;so&amp;amp;#32;preflight&amp;amp;#32;and&amp;amp;#32;training&amp;amp;#32;invoke&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;trusted&amp;amp;#32;executable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;resolve_executable(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learner&amp;amp;#32;=&amp;amp;#32;admin.run_learning&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;learned&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;quot;:&amp;amp;#32;0,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;controller_unavailable&amp;amp;quot;:&amp;amp;#32;False,&amp;amp;#32;&amp;amp;quot;storage_blocked&amp;amp;quot;:&amp;amp;#32;False,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;archive_warning&amp;amp;quot;:&amp;amp;#32;False}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;events&amp;amp;#32;=&amp;amp;#32;order_events(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;len(events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;archive_count&amp;amp;#32;=&amp;amp;#32;_journal(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;preflight(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;candidate&amp;amp;#32;in&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;handled&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;batch_size&amp;amp;#32;or&amp;amp;#32;(stop_event&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;and&amp;amp;#32;stop_event.is_set()):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;candidate[&amp;amp;quot;id&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id).exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Crash&amp;amp;#32;before&amp;amp;#32;prepare_archive&amp;amp;#32;moved&amp;amp;#32;pending/ID:&amp;amp;#32;its&amp;amp;#32;root-owned&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;intent&amp;amp;#32;is&amp;amp;#32;authoritative.&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;pending&amp;amp;#32;original&amp;amp;#32;for&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;manual&amp;amp;#32;recovery&amp;amp;#32;and&amp;amp;#32;continue&amp;amp;#32;independent&amp;amp;#32;content,&amp;amp;#32;never&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;admin.read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;act&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;queued&amp;amp;#32;ordering/identity&amp;amp;#32;changed&amp;amp;#32;since&amp;amp;#32;enumeration.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;any(metadata.get(key)&amp;amp;#32;!=&amp;amp;#32;candidate.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(&amp;amp;quot;sha256&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;+&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label,&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;metadata[&amp;amp;quot;label&amp;amp;quot;],&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_ordering_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;event&amp;amp;#32;ordering&amp;amp;#32;is&amp;amp;#32;ambiguous&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;blocked:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;a&amp;amp;#32;prior&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;#32;has&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;and&amp;amp;#32;latest[digest][1]&amp;amp;#32;==&amp;amp;#32;label:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;same&amp;amp;#32;class&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;latest&amp;amp;#32;successful&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;automatic&amp;amp;#32;explicit&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label,&amp;amp;#32;reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;prepare_archive&amp;amp;#32;writes&amp;amp;#32;a&amp;amp;#32;root&amp;amp;#32;journal&amp;amp;#32;before&amp;amp;#32;moving&amp;amp;#32;the&amp;amp;#32;source;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;stop&amp;amp;#32;instead&amp;amp;#32;of&amp;amp;#32;retrying&amp;amp;#32;an&amp;amp;#32;incompletely&amp;amp;#32;prepared&amp;amp;#32;operation.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt.update({&amp;amp;quot;mode&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;automatic_imap_feedback&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;worker_sequence&amp;amp;quot;:&amp;amp;#32;sequence,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;processed_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&amp;amp;#32;&amp;amp;quot;source_created_ns&amp;amp;quot;:&amp;amp;#32;candidate[&amp;amp;quot;_order_ns&amp;amp;quot;]})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;previous_successful_class&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;latest[digest][1]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Durable&amp;amp;#32;intent&amp;amp;#32;exists&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;call&amp;amp;#32;that&amp;amp;#32;may&amp;amp;#32;change&amp;amp;#32;Bayes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;returncode&amp;amp;#32;=&amp;amp;#32;learner(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;failure;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;returncode&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;admin.collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;((True,&amp;amp;#32;sequence,&amp;amp;#32;event_id),&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;learned&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;duplicates&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive_count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;-=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;feedback_processed&amp;amp;quot;,&amp;amp;#32;id=event_id,&amp;amp;#32;label=label,&amp;amp;#32;status=receipt[&amp;amp;quot;status&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;controller&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;failed&amp;amp;#32;after&amp;amp;#32;the&amp;amp;#32;preflight.&amp;amp;#32;Leave&amp;amp;#32;all&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;other&amp;amp;#32;candidates&amp;amp;#32;pending&amp;amp;#32;until&amp;amp;#32;a&amp;amp;#32;fresh&amp;amp;#32;read-only&amp;amp;#32;probe&amp;amp;#32;passes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--poll&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--batch-size&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=20)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--once&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0.2&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.poll&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;60&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.batch_size&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;100:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;worker&amp;amp;#32;options&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping&amp;amp;#32;=&amp;amp;#32;threading.Event()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;signum&amp;amp;#32;in&amp;amp;#32;(signal.SIGTERM,&amp;amp;#32;signal.SIGINT):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;signal.signal(signum,&amp;amp;#32;lambda&amp;amp;#32;*_:&amp;amp;#32;stopping.set())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;lockfd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.worker.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(lockfd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;not&amp;amp;#32;stopping.is_set():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;process_pending(root,&amp;amp;#32;executable=args.rspamc_bin,&amp;amp;#32;controller=args.controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=args.password_file,&amp;amp;#32;timeout=args.timeout,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;batch_size=args.batch_size,&amp;amp;#32;stop_event=stopping)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;state&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;value&amp;amp;#32;for&amp;amp;#32;key,&amp;amp;#32;value&amp;amp;#32;in&amp;amp;#32;summary.items()&amp;amp;#32;if&amp;amp;#32;key&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;!=&amp;amp;#32;previous_state&amp;amp;#32;or&amp;amp;#32;any(summary[key]&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_state&amp;amp;quot;,&amp;amp;#32;**summary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;state&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;2&amp;amp;#32;if&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;or&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;else&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;previous_state&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_error&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;local&amp;amp;#32;queue,&amp;amp;#32;journal&amp;amp;#32;or&amp;amp;#32;configuration&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;#32;of&amp;amp;#32;uncertain&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping.wait(args.poll)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_start_failed&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;root,&amp;amp;#32;configuration&amp;amp;#32;or&amp;amp;#32;worker&amp;amp;#32;lock&amp;amp;#32;required&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(1)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=677</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=677"/>
		<updated>2026-10-29T23:12:48Z</updated>

		<summary type="html">&lt;p&gt;Bas: Исправлены пробелы и кавычки в блоке rspamd_feedback; язык подсветки sh.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;25&amp;amp;#32;/&amp;amp;#32;STARTTLS&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;465&amp;amp;#32;TLS&amp;amp;#32;или&amp;amp;#32;587&amp;amp;#32;STARTTLS&amp;amp;#32;+&amp;amp;#32;AUTH&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;LMTP&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;/opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;AUTH&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;файловый&amp;amp;#32;каталог&amp;amp;#32;пользователей&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;993&amp;amp;#32;TLS&amp;amp;#32;→&amp;amp;#32;Dovecot&lt;br /&gt;
SnappyMail&amp;amp;#32;(необязательно)&amp;amp;#32;→&amp;amp;#32;те&amp;amp;#32;же&amp;amp;#32;IMAPS&amp;amp;#32;и&amp;amp;#32;SMTP&amp;amp;#32;AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version&amp;amp;#32;-kru&lt;br /&gt;
uname&amp;amp;#32;-m&lt;br /&gt;
hostname&amp;amp;#32;-f&lt;br /&gt;
ifconfig&amp;amp;#32;-a&lt;br /&gt;
netstat&amp;amp;#32;-rn&lt;br /&gt;
cat&amp;amp;#32;/etc/resolv.conf&lt;br /&gt;
mount&amp;amp;#32;-p&lt;br /&gt;
df&amp;amp;#32;-hT&lt;br /&gt;
swapinfo&amp;amp;#32;-h&lt;br /&gt;
geom&amp;amp;#32;disk&amp;amp;#32;list&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-p&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-p&amp;amp;#32;da1&lt;br /&gt;
glabel&amp;amp;#32;status&lt;br /&gt;
fstyp&amp;amp;#32;/dev/da1&lt;br /&gt;
file&amp;amp;#32;-s&amp;amp;#32;/dev/da1&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
ls&amp;amp;#32;-ld&amp;amp;#32;/opt&lt;br /&gt;
cat&amp;amp;#32;/etc/fstab&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sysrc&amp;amp;#32;hostname=mail.example.org&lt;br /&gt;
hostname&amp;amp;#32;mail.example.org&lt;br /&gt;
sysrc&amp;amp;#32;defaultrouter=198.51.100.1&lt;br /&gt;
#&amp;amp;#32;Только&amp;amp;#32;если&amp;amp;#32;в&amp;amp;#32;работающей&amp;amp;#32;таблице&amp;amp;#32;отсутствует&amp;amp;#32;default&amp;amp;#32;route:&lt;br /&gt;
route&amp;amp;#32;add&amp;amp;#32;default&amp;amp;#32;198.51.100.1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo&amp;amp;#32;/dev/da1&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;{print&amp;amp;#32;$3}&#039;)&lt;br /&gt;
test&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;-gt&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;/dev/da1&amp;amp;#32;/dev/zero&lt;br /&gt;
printf&amp;amp;#32;&#039;cmp&amp;amp;#32;status:&amp;amp;#32;%s\n&#039;&amp;amp;#32;&amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;update&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
#&amp;amp;#32;После&amp;amp;#32;проверки&amp;amp;#32;плана:&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
sysrc&amp;amp;#32;exim_enable=NO&amp;amp;#32;dovecot_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_enable=NONE&amp;amp;#32;sendmail_submit_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_outbound_enable=NO&amp;amp;#32;sendmail_msp_queue_enable=NO&lt;br /&gt;
service&amp;amp;#32;sendmail&amp;amp;#32;onestop&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/dovecot&amp;amp;#32;--version&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;mailnull&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;mail&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;review&amp;amp;#32;and&amp;amp;#32;explicit&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback.&lt;br /&gt;
&lt;br /&gt;
No&amp;amp;#32;command&amp;amp;#32;trains&amp;amp;#32;automatically.&amp;amp;#32;list/show&amp;amp;#32;are&amp;amp;#32;local&amp;amp;#32;reads.&amp;amp;#32;learn&amp;amp;#32;ID&amp;amp;#32;spam|ham&lt;br /&gt;
is&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;command&amp;amp;#32;that&amp;amp;#32;invokes&amp;amp;#32;rspamc.&amp;amp;#32;reject&amp;amp;#32;ID&amp;amp;#32;archives&amp;amp;#32;without&amp;amp;#32;training.&lt;br /&gt;
Queue&amp;amp;#32;root&amp;amp;#32;and&amp;amp;#32;processed&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;non-writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users;&lt;br /&gt;
pending&amp;amp;#32;is&amp;amp;#32;separately&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;vmail,&amp;amp;#32;mode&amp;amp;#32;0700.&amp;amp;#32;Archived&amp;amp;#32;originals&amp;amp;#32;remain&amp;amp;#32;in&lt;br /&gt;
processed/ID/source;&amp;amp;#32;only&amp;amp;#32;root-owned&amp;amp;#32;snapshots&amp;amp;#32;are&amp;amp;#32;used&amp;amp;#32;for&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;review.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;email&amp;amp;#32;import&amp;amp;#32;policy&lt;br /&gt;
from&amp;amp;#32;email.parser&amp;amp;#32;import&amp;amp;#32;BytesHeaderParser&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
&lt;br /&gt;
spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_collector&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback.py&amp;amp;quot;))&lt;br /&gt;
collector&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;collector.FeedbackError&lt;br /&gt;
ID_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{32}\Z&amp;amp;quot;)&lt;br /&gt;
DEFAULT_ROOT&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback&amp;amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/usr/local/etc/rspamd/controller.password&amp;amp;quot;&lt;br /&gt;
HEADERS&amp;amp;#32;=&amp;amp;#32;(&amp;amp;quot;From&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;To&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Date&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Subject&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Message-ID&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Status&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Score&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Rspamd-Action&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;emit(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;require_root():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;hasattr(os,&amp;amp;#32;&amp;amp;quot;geteuid&amp;amp;quot;)&amp;amp;#32;or&amp;amp;#32;os.geteuid()&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;administrative&amp;amp;#32;command&amp;amp;#32;requires&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_id(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;ID&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;exactly&amp;amp;#32;32&amp;amp;#32;lowercase&amp;amp;#32;hexadecimal&amp;amp;#32;characters&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;Directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Nofollow&amp;amp;#32;directory&amp;amp;#32;descriptor&amp;amp;#32;on&amp;amp;#32;FreeBSD;&amp;amp;#32;portable&amp;amp;#32;fallback&amp;amp;#32;for&amp;amp;#32;local&amp;amp;#32;tests.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__init__(self,&amp;amp;#32;path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;self.path.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;expected&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;os.open(self.path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__enter__(self):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;self&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__exit__(self,&amp;amp;#32;*args):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;read(self,&amp;amp;#32;name,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;&amp;amp;quot;\\&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;.&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;..&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;internal&amp;amp;#32;filename&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(name,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW&amp;amp;#32;|&amp;amp;#32;os.O_NONBLOCK,&amp;amp;#32;dir_fd=self.fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;self.path&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;symlink&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;event&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;rb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;source:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.fstat(source.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;info.st_nlink&amp;amp;#32;!=&amp;amp;#32;1&amp;amp;#32;or&amp;amp;#32;info.st_size&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;bounded,&amp;amp;#32;single-link&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;source.read(limit&amp;amp;#32;+&amp;amp;#32;1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(result)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;exceeded&amp;amp;#32;the&amp;amp;#32;size&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;read_event(directory,&amp;amp;#32;event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;On&amp;amp;#32;POSIX&amp;amp;#32;open&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;relative&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;already&amp;amp;#32;opened&amp;amp;#32;parent&amp;amp;#32;descriptor.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;parent&amp;amp;#32;path&amp;amp;#32;is&amp;amp;#32;deployment-owned,&amp;amp;#32;and&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;event&amp;amp;#32;is&amp;amp;#32;rejected.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(Path(directory)&amp;amp;#32;/&amp;amp;#32;event_id)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;entry.read(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;MAX_MESSAGE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;user&amp;amp;quot;),&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;event_id&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;bytes&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;len(message)&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;hashlib.sha256(message).hexdigest():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;content&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;its&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata,&amp;amp;#32;message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write_private(path,&amp;amp;#32;value,&amp;amp;#32;*,&amp;amp;#32;json_value=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;flags,&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;json_value:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;value&amp;amp;#32;=&amp;amp;#32;(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;\n&amp;amp;quot;).encode()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(value)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;update_receipt(directory,&amp;amp;#32;receipt):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin-new.json&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(temporary,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.replace(temporary,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_root_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;Path(path).stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;administrative&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;group/other&amp;amp;#32;writable&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;receipt_for(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;find_prior(root,&amp;amp;#32;sha256):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;damaged&amp;amp;#32;journal&amp;amp;#32;must&amp;amp;#32;never&amp;amp;#32;silently&amp;amp;#32;allow&amp;amp;#32;repeated&amp;amp;#32;training.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;is&amp;amp;#32;incomplete;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;before&amp;amp;#32;more&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;==&amp;amp;#32;sha256&amp;amp;#32;and&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label=None,&amp;amp;#32;reason=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(root).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;collector.MIN_FREE_BYTES&amp;amp;#32;+&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;#32;to&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;reviewed&amp;amp;#32;snapshot&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;],&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;reviewed_utc&amp;amp;quot;:&amp;amp;#32;collector.utc_now(),&amp;amp;#32;&amp;amp;quot;status&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;preparing&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;selected_class&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;reason&amp;amp;quot;:&amp;amp;#32;reason}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;json.dumps(metadata,&amp;amp;#32;ensure_ascii=True).encode())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;complete&amp;amp;#32;original&amp;amp;#32;event.&amp;amp;#32;Never&amp;amp;#32;traverse&amp;amp;#32;or&amp;amp;#32;execute&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;source/.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id,&amp;amp;#32;archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;status&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;archive,&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;executable.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;path&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[str(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;rspamc&amp;amp;#32;reads&amp;amp;#32;the&amp;amp;#32;file&amp;amp;#32;itself;&amp;amp;#32;the&amp;amp;#32;secret&amp;amp;#32;never&amp;amp;#32;appears&amp;amp;#32;in&amp;amp;#32;argv&amp;amp;#32;or&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;learn_&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;-u/-d:&amp;amp;#32;one&amp;amp;#32;shared&amp;amp;#32;Bayes&amp;amp;#32;classifier.&amp;amp;#32;No&amp;amp;#32;shell&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;input=message,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;timeout=timeout,&amp;amp;#32;check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;learn_event(root,&amp;amp;#32;event_id,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;training&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;prior&amp;amp;#32;=&amp;amp;#32;find_prior(root,&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prior:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;prior&amp;amp;#32;learned&amp;amp;#32;or&amp;amp;#32;uncertain&amp;amp;#32;event;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;and&amp;amp;#32;reject&amp;amp;#32;the&amp;amp;#32;duplicate&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rc&amp;amp;#32;=&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;subprocess.TimeoutExpired:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;timeout;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;automatically&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_failed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;could&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;or&amp;amp;#32;local&amp;amp;#32;configuration&amp;amp;#32;was&amp;amp;#32;invalid&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;rc&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;rc&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;rc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;inspect&amp;amp;#32;server&amp;amp;#32;before&amp;amp;#32;any&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;reject_event(root,&amp;amp;#32;event_id,&amp;amp;#32;reason=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(reason)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;500:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;reason&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;rejected&amp;amp;quot;,&amp;amp;#32;reason=reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;list_events(root,&amp;amp;#32;state,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;((&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;(state,)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;directory.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;directory.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;notice&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;limit&amp;amp;#32;reached&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;limit&amp;amp;quot;:&amp;amp;#32;limit})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(entry)&amp;amp;#32;as&amp;amp;#32;item:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(item.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;metadata.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;id&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;state&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;review&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(row)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;state&amp;amp;quot;:&amp;amp;#32;name,&amp;amp;#32;&amp;amp;quot;error&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;#32;event;&amp;amp;#32;manual&amp;amp;#32;inspection&amp;amp;#32;required&amp;amp;quot;})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;show_event(root,&amp;amp;#32;event_id,&amp;amp;#32;state,&amp;amp;#32;path_only):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;state,&amp;amp;#32;check_id(event_id))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path_only:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;message_path&amp;amp;quot;:&amp;amp;#32;str(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;)})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;headers&amp;amp;#32;=&amp;amp;#32;BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message),&amp;amp;#32;262144)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;metadata&amp;amp;quot;:&amp;amp;#32;metadata,&amp;amp;#32;&amp;amp;quot;headers&amp;amp;quot;:&amp;amp;#32;{name:&amp;amp;#32;[str(v)[:2000]&amp;amp;#32;for&amp;amp;#32;v&amp;amp;#32;in&amp;amp;#32;headers.get_all(name,&amp;amp;#32;[])[:10]]&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;HEADERS}})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;review&amp;amp;quot;:&amp;amp;#32;receipt_for(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;warn_processed_storage(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;event&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event.name)&amp;amp;#32;or&amp;amp;#32;event.is_symlink()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;event.is_dir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;prefix&amp;amp;#32;in&amp;amp;#32;(event,&amp;amp;#32;event&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prefix.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(prefix&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_BYTES&amp;amp;#32;or&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;WARNING&amp;amp;#32;processed&amp;amp;#32;archive&amp;amp;#32;reached&amp;amp;#32;512&amp;amp;#32;MiB&amp;amp;#32;or&amp;amp;#32;1024&amp;amp;#32;events;&amp;amp;#32;review&amp;amp;#32;retention&amp;amp;#32;manually;&amp;amp;#32;nothing&amp;amp;#32;deleted&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sub&amp;amp;#32;=&amp;amp;#32;parser.add_subparsers(dest=&amp;amp;quot;command&amp;amp;quot;,&amp;amp;#32;required=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;list&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;JSON&amp;amp;#32;lines,&amp;amp;#32;metadata&amp;amp;#32;only&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--limit&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=100)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;show&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;selected&amp;amp;#32;headers&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;dumps&amp;amp;#32;the&amp;amp;#32;body&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--path&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;learn&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;explicitly&amp;amp;#32;train&amp;amp;#32;one&amp;amp;#32;reviewed&amp;amp;#32;event,&amp;amp;#32;then&amp;amp;#32;archive&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;help=&amp;amp;quot;root-only&amp;amp;#32;plaintext&amp;amp;#32;secret&amp;amp;#32;file;&amp;amp;#32;use&amp;amp;#32;--no-password&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;an&amp;amp;#32;intentionally&amp;amp;#32;trusted&amp;amp;#32;local&amp;amp;#32;controller&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--no-password&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;reject&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;archive&amp;amp;#32;one&amp;amp;#32;event&amp;amp;#32;without&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;deleting&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;--reason&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;root&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn_processed_storage(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;list&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.limit&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;10000:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;limit&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;1&amp;amp;#32;and&amp;amp;#32;10000&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;list_events(root,&amp;amp;#32;args.state,&amp;amp;#32;args.limit)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;show&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;show_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.state,&amp;amp;#32;args.path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;reject&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(reject_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.reason))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;timeout&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;0&amp;amp;#32;and&amp;amp;#32;300&amp;amp;#32;seconds&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;learn_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.label,&amp;amp;#32;executable=args.rspamc_bin,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;controller=args.controller,&amp;amp;#32;password_file=None&amp;amp;#32;if&amp;amp;#32;args.no_password&amp;amp;#32;else&amp;amp;#32;args.password_file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timeout=args.timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(result)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&amp;amp;#32;if&amp;amp;#32;result[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;operation&amp;amp;#32;refused&amp;amp;#32;or&amp;amp;#32;failed;&amp;amp;#32;inspect&amp;amp;#32;configuration/event&amp;amp;#32;locally&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;background&amp;amp;#32;learning&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;bounded&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback&amp;amp;#32;queue.&lt;br /&gt;
&lt;br /&gt;
The&amp;amp;#32;collector&amp;amp;#32;remains&amp;amp;#32;a&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;writer.&amp;amp;#32;This&amp;amp;#32;process&amp;amp;#32;owns&amp;amp;#32;controller&lt;br /&gt;
credentials,&amp;amp;#32;learns&amp;amp;#32;only&amp;amp;#32;validated&amp;amp;#32;INBOX/Junk&amp;amp;#32;events,&amp;amp;#32;preserves&amp;amp;#32;every&amp;amp;#32;original,&lt;br /&gt;
and&amp;amp;#32;never&amp;amp;#32;retries&amp;amp;#32;a&amp;amp;#32;training&amp;amp;#32;operation&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result.&amp;amp;#32;Run&amp;amp;#32;foreground&lt;br /&gt;
under&amp;amp;#32;FreeBSD&amp;amp;#32;daemon(8),&amp;amp;#32;or&amp;amp;#32;use&amp;amp;#32;--once&amp;amp;#32;for&amp;amp;#32;one&amp;amp;#32;bounded&amp;amp;#32;batch.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;signal&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;threading&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
&lt;br /&gt;
_spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_worker_admin&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback-admin.py&amp;amp;quot;))&lt;br /&gt;
admin&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;admin.FeedbackError&lt;br /&gt;
SHA_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{64}\Z&amp;amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
UNCERTAIN&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;log_event(kind,&amp;amp;#32;**fields):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Callers&amp;amp;#32;pass&amp;amp;#32;only&amp;amp;#32;fixed&amp;amp;#32;diagnostics,&amp;amp;#32;numeric&amp;amp;#32;counts,&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;classes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps({&amp;amp;quot;event&amp;amp;quot;:&amp;amp;#32;kind,&amp;amp;#32;**fields},&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True),&amp;amp;#32;flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_timestamp_ns(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(value,&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;missing&amp;amp;#32;timestamp&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parsed&amp;amp;#32;=&amp;amp;#32;datetime.fromisoformat(value.replace(&amp;amp;quot;Z&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;+00:00&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;parsed.tzinfo&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;timestamp&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;timezone&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;int(parsed.timestamp()&amp;amp;#32;*&amp;amp;#32;1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;order_events(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;bounded&amp;amp;#32;metadata,&amp;amp;#32;ordered&amp;amp;#32;by&amp;amp;#32;collector&amp;amp;#32;created_ns,&amp;amp;#32;then&amp;amp;#32;event&amp;amp;#32;ID.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;Legacy&amp;amp;#32;second-resolution&amp;amp;#32;timestamps&amp;amp;#32;are&amp;amp;#32;permitted&amp;amp;#32;only&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;same&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;contradictory&amp;amp;#32;labels&amp;amp;#32;at&amp;amp;#32;an&amp;amp;#32;identical&amp;amp;#32;timestamp.&amp;amp;#32;Such&amp;amp;#32;a&amp;amp;#32;tie&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cannot&amp;amp;#32;be&amp;amp;#32;resolved&amp;amp;#32;from&amp;amp;#32;random&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;is&amp;amp;#32;quarantined&amp;amp;#32;without&amp;amp;#32;learning.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pending&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows&amp;amp;#32;=&amp;amp;#32;[]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(pending)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;queue&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending&amp;amp;#32;/&amp;amp;#32;entry.name)&amp;amp;#32;as&amp;amp;#32;directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(directory.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;dict(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;sha256&amp;amp;quot;),&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(metadata[&amp;amp;quot;sha256&amp;amp;quot;]):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timestamp&amp;amp;#32;=&amp;amp;#32;metadata.get(&amp;amp;quot;created_ns&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;int)&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;bool)&amp;amp;#32;and&amp;amp;#32;timestamp&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;timestamp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;metadata:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(metadata.get(&amp;amp;quot;created_utc&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;manual&amp;amp;#32;administrator&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;archived&amp;amp;#32;this&amp;amp;#32;candidate.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;_invalid&amp;amp;quot;:&amp;amp;#32;True,&amp;amp;#32;&amp;amp;quot;_order_ns&amp;amp;quot;:&amp;amp;#32;0})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied&amp;amp;#32;=&amp;amp;#32;{}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied.setdefault((row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;]),&amp;amp;#32;set()).add(row[&amp;amp;quot;label&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;)&amp;amp;#32;and&amp;amp;#32;len(tied[(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;])])&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;sorted(rows,&amp;amp;#32;key=lambda&amp;amp;#32;row:&amp;amp;#32;(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;id&amp;amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_journal(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;root-owned&amp;amp;#32;receipts;&amp;amp;#32;successful&amp;amp;#32;classes&amp;amp;#32;use&amp;amp;#32;durable&amp;amp;#32;worker&amp;amp;#32;sequence.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked&amp;amp;#32;=&amp;amp;#32;{},&amp;amp;#32;set()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;used_bytes&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(processed)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;MAX_JOURNAL_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;processed&amp;amp;#32;/&amp;amp;#32;entry.name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.receipt_for(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(receipt,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;receipt.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;receipt&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(digest,&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(digest):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;worker_sequence&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(sequence,&amp;amp;#32;int)&amp;amp;#32;or&amp;amp;#32;isinstance(sequence,&amp;amp;#32;bool)&amp;amp;#32;or&amp;amp;#32;sequence&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;sequence&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;max(max_sequence,&amp;amp;#32;sequence)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;in&amp;amp;#32;UNCERTAIN:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;selected_class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;learned&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Existing&amp;amp;#32;manual&amp;amp;#32;receipts&amp;amp;#32;predate&amp;amp;#32;automatic&amp;amp;#32;processing;&amp;amp;#32;the&amp;amp;#32;old&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;admin&amp;amp;#32;refuses&amp;amp;#32;further&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;a&amp;amp;#32;SHA&amp;amp;#32;once&amp;amp;#32;it&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;receipt.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fallback&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(receipt.get(&amp;amp;quot;finished_utc&amp;amp;quot;,&amp;amp;#32;receipt.get(&amp;amp;quot;reviewed_utc&amp;amp;quot;)))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;position&amp;amp;#32;=&amp;amp;#32;(bool(sequence),&amp;amp;#32;sequence&amp;amp;#32;if&amp;amp;#32;sequence&amp;amp;#32;else&amp;amp;#32;fallback,&amp;amp;#32;entry.name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;or&amp;amp;#32;position&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;latest[digest][0]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;(position,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Count&amp;amp;#32;snapshots&amp;amp;#32;and&amp;amp;#32;preserved&amp;amp;#32;originals,&amp;amp;#32;never&amp;amp;#32;recursively&amp;amp;#32;walk&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;arbitrary&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail-user-owned&amp;amp;#32;source/&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;part&amp;amp;#32;in&amp;amp;#32;(directory,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(part)&amp;amp;#32;as&amp;amp;#32;safe:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;safe.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.stat(name,&amp;amp;#32;dir_fd=safe.fd,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(part&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_check_trusted_parent_chain(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;parent&amp;amp;#32;in&amp;amp;#32;Path(path).parents:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;link_info&amp;amp;#32;=&amp;amp;#32;parent.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;parent.stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(link_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;or&amp;amp;#32;info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;executable&amp;amp;#32;parent&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;controlled&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;resolve_executable(executable):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Allow&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;symlinks,&amp;amp;#32;but&amp;amp;#32;pin&amp;amp;#32;both&amp;amp;#32;operations&amp;amp;#32;to&amp;amp;#32;their&amp;amp;#32;target.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;source.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source_info&amp;amp;#32;=&amp;amp;#32;source.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;source_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;entry&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(source)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target&amp;amp;#32;=&amp;amp;#32;source.resolve(strict=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;target.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(target)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;controller_ready(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Authenticated&amp;amp;#32;read-only&amp;amp;#32;controller&amp;amp;#32;probe;&amp;amp;#32;no&amp;amp;#32;candidate&amp;amp;#32;is&amp;amp;#32;archived&amp;amp;#32;yet.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[resolve_executable(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unsafe&amp;amp;#32;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;stat&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;stdin=subprocess.DEVNULL,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;check=False,&amp;amp;#32;timeout=min(timeout,&amp;amp;#32;5)).returncode&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;process_pending(root,&amp;amp;#32;*,&amp;amp;#32;executable=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;,&amp;amp;#32;controller=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=admin.DEFAULT_PASSWORD_FILE,&amp;amp;#32;timeout=30,&amp;amp;#32;batch_size=20,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight=None,&amp;amp;#32;learner=None,&amp;amp;#32;stop_event=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Process&amp;amp;#32;at&amp;amp;#32;most&amp;amp;#32;batch_size&amp;amp;#32;candidates.&amp;amp;#32;Inject&amp;amp;#32;probes/learner&amp;amp;#32;for&amp;amp;#32;unit&amp;amp;#32;tests.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;One&amp;amp;#32;admin&amp;amp;#32;lock&amp;amp;#32;serializes&amp;amp;#32;manual&amp;amp;#32;and&amp;amp;#32;background&amp;amp;#32;operations.&amp;amp;#32;A&amp;amp;#32;crash&amp;amp;#32;after&amp;amp;#32;a&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;durable&amp;amp;#32;learning&amp;amp;#32;intent&amp;amp;#32;leaves&amp;amp;#32;status=learning,&amp;amp;#32;which&amp;amp;#32;blocks&amp;amp;#32;this&amp;amp;#32;SHA&amp;amp;#32;on&amp;amp;#32;the&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;next&amp;amp;#32;pass;&amp;amp;#32;subsequent&amp;amp;#32;candidates&amp;amp;#32;are&amp;amp;#32;preserved&amp;amp;#32;as&amp;amp;#32;quarantined_uncertain.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight&amp;amp;#32;=&amp;amp;#32;controller_ready&amp;amp;#32;if&amp;amp;#32;preflight&amp;amp;#32;is&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;preflight&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;learner&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;pkg&amp;amp;#32;installs&amp;amp;#32;rspamc&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;root-owned&amp;amp;#32;versioned&amp;amp;#32;symlink.&amp;amp;#32;Resolve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;it&amp;amp;#32;once&amp;amp;#32;so&amp;amp;#32;preflight&amp;amp;#32;and&amp;amp;#32;training&amp;amp;#32;invoke&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;trusted&amp;amp;#32;executable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;resolve_executable(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learner&amp;amp;#32;=&amp;amp;#32;admin.run_learning&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;learned&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;quot;:&amp;amp;#32;0,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;controller_unavailable&amp;amp;quot;:&amp;amp;#32;False,&amp;amp;#32;&amp;amp;quot;storage_blocked&amp;amp;quot;:&amp;amp;#32;False,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;archive_warning&amp;amp;quot;:&amp;amp;#32;False}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;events&amp;amp;#32;=&amp;amp;#32;order_events(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;len(events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;archive_count&amp;amp;#32;=&amp;amp;#32;_journal(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;preflight(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;candidate&amp;amp;#32;in&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;handled&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;batch_size&amp;amp;#32;or&amp;amp;#32;(stop_event&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;and&amp;amp;#32;stop_event.is_set()):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;candidate[&amp;amp;quot;id&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id).exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Crash&amp;amp;#32;before&amp;amp;#32;prepare_archive&amp;amp;#32;moved&amp;amp;#32;pending/ID:&amp;amp;#32;its&amp;amp;#32;root-owned&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;intent&amp;amp;#32;is&amp;amp;#32;authoritative.&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;pending&amp;amp;#32;original&amp;amp;#32;for&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;manual&amp;amp;#32;recovery&amp;amp;#32;and&amp;amp;#32;continue&amp;amp;#32;independent&amp;amp;#32;content,&amp;amp;#32;never&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;admin.read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;act&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;queued&amp;amp;#32;ordering/identity&amp;amp;#32;changed&amp;amp;#32;since&amp;amp;#32;enumeration.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;any(metadata.get(key)&amp;amp;#32;!=&amp;amp;#32;candidate.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(&amp;amp;quot;sha256&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;+&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label,&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;metadata[&amp;amp;quot;label&amp;amp;quot;],&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_ordering_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;event&amp;amp;#32;ordering&amp;amp;#32;is&amp;amp;#32;ambiguous&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;blocked:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;a&amp;amp;#32;prior&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;#32;has&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;and&amp;amp;#32;latest[digest][1]&amp;amp;#32;==&amp;amp;#32;label:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;same&amp;amp;#32;class&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;latest&amp;amp;#32;successful&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;automatic&amp;amp;#32;explicit&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label,&amp;amp;#32;reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;prepare_archive&amp;amp;#32;writes&amp;amp;#32;a&amp;amp;#32;root&amp;amp;#32;journal&amp;amp;#32;before&amp;amp;#32;moving&amp;amp;#32;the&amp;amp;#32;source;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;stop&amp;amp;#32;instead&amp;amp;#32;of&amp;amp;#32;retrying&amp;amp;#32;an&amp;amp;#32;incompletely&amp;amp;#32;prepared&amp;amp;#32;operation.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt.update({&amp;amp;quot;mode&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;automatic_imap_feedback&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;worker_sequence&amp;amp;quot;:&amp;amp;#32;sequence,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;processed_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&amp;amp;#32;&amp;amp;quot;source_created_ns&amp;amp;quot;:&amp;amp;#32;candidate[&amp;amp;quot;_order_ns&amp;amp;quot;]})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;previous_successful_class&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;latest[digest][1]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Durable&amp;amp;#32;intent&amp;amp;#32;exists&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;call&amp;amp;#32;that&amp;amp;#32;may&amp;amp;#32;change&amp;amp;#32;Bayes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;returncode&amp;amp;#32;=&amp;amp;#32;learner(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;failure;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;returncode&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;admin.collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;((True,&amp;amp;#32;sequence,&amp;amp;#32;event_id),&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;learned&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;duplicates&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive_count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;-=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;feedback_processed&amp;amp;quot;,&amp;amp;#32;id=event_id,&amp;amp;#32;label=label,&amp;amp;#32;status=receipt[&amp;amp;quot;status&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;controller&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;failed&amp;amp;#32;after&amp;amp;#32;the&amp;amp;#32;preflight.&amp;amp;#32;Leave&amp;amp;#32;all&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;other&amp;amp;#32;candidates&amp;amp;#32;pending&amp;amp;#32;until&amp;amp;#32;a&amp;amp;#32;fresh&amp;amp;#32;read-only&amp;amp;#32;probe&amp;amp;#32;passes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--poll&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--batch-size&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=20)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--once&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0.2&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.poll&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;60&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.batch_size&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;100:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;worker&amp;amp;#32;options&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping&amp;amp;#32;=&amp;amp;#32;threading.Event()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;signum&amp;amp;#32;in&amp;amp;#32;(signal.SIGTERM,&amp;amp;#32;signal.SIGINT):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;signal.signal(signum,&amp;amp;#32;lambda&amp;amp;#32;*_:&amp;amp;#32;stopping.set())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;lockfd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.worker.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(lockfd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;not&amp;amp;#32;stopping.is_set():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;process_pending(root,&amp;amp;#32;executable=args.rspamc_bin,&amp;amp;#32;controller=args.controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=args.password_file,&amp;amp;#32;timeout=args.timeout,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;batch_size=args.batch_size,&amp;amp;#32;stop_event=stopping)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;state&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;value&amp;amp;#32;for&amp;amp;#32;key,&amp;amp;#32;value&amp;amp;#32;in&amp;amp;#32;summary.items()&amp;amp;#32;if&amp;amp;#32;key&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;!=&amp;amp;#32;previous_state&amp;amp;#32;or&amp;amp;#32;any(summary[key]&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_state&amp;amp;quot;,&amp;amp;#32;**summary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;state&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;2&amp;amp;#32;if&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;or&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;else&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;previous_state&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_error&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;local&amp;amp;#32;queue,&amp;amp;#32;journal&amp;amp;#32;or&amp;amp;#32;configuration&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;#32;of&amp;amp;#32;uncertain&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping.wait(args.poll)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_start_failed&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;root,&amp;amp;#32;configuration&amp;amp;#32;or&amp;amp;#32;worker&amp;amp;#32;lock&amp;amp;#32;required&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(1)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot; copy&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
# PROVIDE: rspamd_feedback&lt;br /&gt;
# REQUIRE: LOGIN redis rspamd&lt;br /&gt;
# KEYWORD: shutdown&lt;br /&gt;
#&lt;br /&gt;
# Install as /usr/local/etc/rc.d/rspamd_feedback, root:wheel 0555.&lt;br /&gt;
# Enable with: sysrc rspamd_feedback_enable=YES&lt;br /&gt;
# Uses FreeBSD 15.1 daemon(8): rc.subr targets the supervisor PID (-P),&lt;br /&gt;
# not the child PID (-p), so stop cannot accidentally restart the worker.&lt;br /&gt;
&lt;br /&gt;
. /etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;quot;rspamd_feedback&amp;quot;&lt;br /&gt;
desc=&amp;quot;Apply user spam/ham feedback to the local Rspamd Bayes classifier&amp;quot;&lt;br /&gt;
rcvar=&amp;quot;rspamd_feedback_enable&amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config &amp;quot;${name}&amp;quot;&lt;br /&gt;
: ${rspamd_feedback_enable:=&amp;quot;NO&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
procname=&amp;quot;/usr/sbin/daemon&amp;quot;&lt;br /&gt;
pidfile=&amp;quot;/var/run/rspamd_feedback.pid&amp;quot;&lt;br /&gt;
child_pidfile=&amp;quot;/var/run/rspamd_feedback.child.pid&amp;quot;&lt;br /&gt;
worker=&amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;quot;&lt;br /&gt;
python=&amp;quot;/usr/local/bin/python3.12&amp;quot;&lt;br /&gt;
logdir=&amp;quot;/opt/mail/log/rspamd-feedback&amp;quot;&lt;br /&gt;
logfile=&amp;quot;${logdir}/worker.log&amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;quot;${python} ${worker} /usr/local/libexec/rspamd-feedback/feedback-admin.py /usr/local/etc/rspamd/controller.password&amp;quot;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/rspamd-feedback/pending /opt/mail/rspamd-feedback/processed /opt/mail/rspamd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Worker handles transient controller errors in its polling loop. Unexpected&lt;br /&gt;
# exits get at most five supervised restarts, spaced 15 seconds apart.&lt;br /&gt;
# -H reopens the log on SIGHUP without interrupting an in-flight learn request.&lt;br /&gt;
command_args=&amp;quot;-c -f -H -R 15 -C 5 -P ${pidfile} -p ${child_pidfile} -t ${name} -o ${logfile} ${python} -u ${worker} --poll 2&amp;quot;&lt;br /&gt;
start_precmd=&amp;quot;rspamd_feedback_prestart&amp;quot;&lt;br /&gt;
extra_commands=&amp;quot;reload&amp;quot;&lt;br /&gt;
sig_reload=&amp;quot;HUP&amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
    if [ &amp;quot;$(id -u)&amp;quot; -ne 0 ]; then&lt;br /&gt;
        warn &amp;quot;${name} requires root to read the local controller credential&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    # Refuse to write queue/log data to the root filesystem when /opt failed&lt;br /&gt;
    # to mount. Directory existence alone does not establish the mount state.&lt;br /&gt;
    if ! /sbin/mount -p | /usr/bin/awk &#039;$2 == &amp;quot;/opt&amp;quot; { found = 1 } END { exit !found }&#039;; then&lt;br /&gt;
        warn &amp;quot;${name} requires the mail filesystem mounted at /opt&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ -L &amp;quot;${logdir}&amp;quot; ] || [ -L &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        warn &amp;quot;${name} refuses a symlink at its private log path&amp;quot;&lt;br /&gt;
        return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/bin/install -d -o root -g wheel -m 0700 &amp;quot;${logdir}&amp;quot; || return 1&lt;br /&gt;
    if [ ! -e &amp;quot;${logfile}&amp;quot; ]; then&lt;br /&gt;
        /usr/bin/install -o root -g wheel -m 0600 /dev/null &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    fi&lt;br /&gt;
    /usr/sbin/chown root:wheel &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    /bin/chmod 0600 &amp;quot;${logfile}&amp;quot; || return 1&lt;br /&gt;
    umask 077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command &amp;quot;$1&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=676</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=676"/>
		<updated>2026-10-29T23:02:28Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 15.6. Запустить автоматический обработчик */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;25&amp;amp;#32;/&amp;amp;#32;STARTTLS&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;465&amp;amp;#32;TLS&amp;amp;#32;или&amp;amp;#32;587&amp;amp;#32;STARTTLS&amp;amp;#32;+&amp;amp;#32;AUTH&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;LMTP&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;/opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;AUTH&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;файловый&amp;amp;#32;каталог&amp;amp;#32;пользователей&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;993&amp;amp;#32;TLS&amp;amp;#32;→&amp;amp;#32;Dovecot&lt;br /&gt;
SnappyMail&amp;amp;#32;(необязательно)&amp;amp;#32;→&amp;amp;#32;те&amp;amp;#32;же&amp;amp;#32;IMAPS&amp;amp;#32;и&amp;amp;#32;SMTP&amp;amp;#32;AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version&amp;amp;#32;-kru&lt;br /&gt;
uname&amp;amp;#32;-m&lt;br /&gt;
hostname&amp;amp;#32;-f&lt;br /&gt;
ifconfig&amp;amp;#32;-a&lt;br /&gt;
netstat&amp;amp;#32;-rn&lt;br /&gt;
cat&amp;amp;#32;/etc/resolv.conf&lt;br /&gt;
mount&amp;amp;#32;-p&lt;br /&gt;
df&amp;amp;#32;-hT&lt;br /&gt;
swapinfo&amp;amp;#32;-h&lt;br /&gt;
geom&amp;amp;#32;disk&amp;amp;#32;list&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-p&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-p&amp;amp;#32;da1&lt;br /&gt;
glabel&amp;amp;#32;status&lt;br /&gt;
fstyp&amp;amp;#32;/dev/da1&lt;br /&gt;
file&amp;amp;#32;-s&amp;amp;#32;/dev/da1&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
ls&amp;amp;#32;-ld&amp;amp;#32;/opt&lt;br /&gt;
cat&amp;amp;#32;/etc/fstab&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sysrc&amp;amp;#32;hostname=mail.example.org&lt;br /&gt;
hostname&amp;amp;#32;mail.example.org&lt;br /&gt;
sysrc&amp;amp;#32;defaultrouter=198.51.100.1&lt;br /&gt;
#&amp;amp;#32;Только&amp;amp;#32;если&amp;amp;#32;в&amp;amp;#32;работающей&amp;amp;#32;таблице&amp;amp;#32;отсутствует&amp;amp;#32;default&amp;amp;#32;route:&lt;br /&gt;
route&amp;amp;#32;add&amp;amp;#32;default&amp;amp;#32;198.51.100.1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo&amp;amp;#32;/dev/da1&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;{print&amp;amp;#32;$3}&#039;)&lt;br /&gt;
test&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;-gt&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;/dev/da1&amp;amp;#32;/dev/zero&lt;br /&gt;
printf&amp;amp;#32;&#039;cmp&amp;amp;#32;status:&amp;amp;#32;%s\n&#039;&amp;amp;#32;&amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;update&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
#&amp;amp;#32;После&amp;amp;#32;проверки&amp;amp;#32;плана:&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
sysrc&amp;amp;#32;exim_enable=NO&amp;amp;#32;dovecot_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_enable=NONE&amp;amp;#32;sendmail_submit_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_outbound_enable=NO&amp;amp;#32;sendmail_msp_queue_enable=NO&lt;br /&gt;
service&amp;amp;#32;sendmail&amp;amp;#32;onestop&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/dovecot&amp;amp;#32;--version&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;mailnull&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;mail&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;review&amp;amp;#32;and&amp;amp;#32;explicit&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback.&lt;br /&gt;
&lt;br /&gt;
No&amp;amp;#32;command&amp;amp;#32;trains&amp;amp;#32;automatically.&amp;amp;#32;list/show&amp;amp;#32;are&amp;amp;#32;local&amp;amp;#32;reads.&amp;amp;#32;learn&amp;amp;#32;ID&amp;amp;#32;spam|ham&lt;br /&gt;
is&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;command&amp;amp;#32;that&amp;amp;#32;invokes&amp;amp;#32;rspamc.&amp;amp;#32;reject&amp;amp;#32;ID&amp;amp;#32;archives&amp;amp;#32;without&amp;amp;#32;training.&lt;br /&gt;
Queue&amp;amp;#32;root&amp;amp;#32;and&amp;amp;#32;processed&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;non-writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users;&lt;br /&gt;
pending&amp;amp;#32;is&amp;amp;#32;separately&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;vmail,&amp;amp;#32;mode&amp;amp;#32;0700.&amp;amp;#32;Archived&amp;amp;#32;originals&amp;amp;#32;remain&amp;amp;#32;in&lt;br /&gt;
processed/ID/source;&amp;amp;#32;only&amp;amp;#32;root-owned&amp;amp;#32;snapshots&amp;amp;#32;are&amp;amp;#32;used&amp;amp;#32;for&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;review.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;email&amp;amp;#32;import&amp;amp;#32;policy&lt;br /&gt;
from&amp;amp;#32;email.parser&amp;amp;#32;import&amp;amp;#32;BytesHeaderParser&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
&lt;br /&gt;
spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_collector&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback.py&amp;amp;quot;))&lt;br /&gt;
collector&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;collector.FeedbackError&lt;br /&gt;
ID_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{32}\Z&amp;amp;quot;)&lt;br /&gt;
DEFAULT_ROOT&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback&amp;amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/usr/local/etc/rspamd/controller.password&amp;amp;quot;&lt;br /&gt;
HEADERS&amp;amp;#32;=&amp;amp;#32;(&amp;amp;quot;From&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;To&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Date&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Subject&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Message-ID&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Status&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Score&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Rspamd-Action&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;emit(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;require_root():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;hasattr(os,&amp;amp;#32;&amp;amp;quot;geteuid&amp;amp;quot;)&amp;amp;#32;or&amp;amp;#32;os.geteuid()&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;administrative&amp;amp;#32;command&amp;amp;#32;requires&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_id(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;ID&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;exactly&amp;amp;#32;32&amp;amp;#32;lowercase&amp;amp;#32;hexadecimal&amp;amp;#32;characters&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;Directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Nofollow&amp;amp;#32;directory&amp;amp;#32;descriptor&amp;amp;#32;on&amp;amp;#32;FreeBSD;&amp;amp;#32;portable&amp;amp;#32;fallback&amp;amp;#32;for&amp;amp;#32;local&amp;amp;#32;tests.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__init__(self,&amp;amp;#32;path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;self.path.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;expected&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;os.open(self.path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__enter__(self):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;self&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__exit__(self,&amp;amp;#32;*args):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;read(self,&amp;amp;#32;name,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;&amp;amp;quot;\\&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;.&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;..&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;internal&amp;amp;#32;filename&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(name,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW&amp;amp;#32;|&amp;amp;#32;os.O_NONBLOCK,&amp;amp;#32;dir_fd=self.fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;self.path&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;symlink&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;event&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;rb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;source:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.fstat(source.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;info.st_nlink&amp;amp;#32;!=&amp;amp;#32;1&amp;amp;#32;or&amp;amp;#32;info.st_size&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;bounded,&amp;amp;#32;single-link&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;source.read(limit&amp;amp;#32;+&amp;amp;#32;1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(result)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;exceeded&amp;amp;#32;the&amp;amp;#32;size&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;read_event(directory,&amp;amp;#32;event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;On&amp;amp;#32;POSIX&amp;amp;#32;open&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;relative&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;already&amp;amp;#32;opened&amp;amp;#32;parent&amp;amp;#32;descriptor.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;parent&amp;amp;#32;path&amp;amp;#32;is&amp;amp;#32;deployment-owned,&amp;amp;#32;and&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;event&amp;amp;#32;is&amp;amp;#32;rejected.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(Path(directory)&amp;amp;#32;/&amp;amp;#32;event_id)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;entry.read(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;MAX_MESSAGE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;user&amp;amp;quot;),&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;event_id&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;bytes&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;len(message)&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;hashlib.sha256(message).hexdigest():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;content&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;its&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata,&amp;amp;#32;message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write_private(path,&amp;amp;#32;value,&amp;amp;#32;*,&amp;amp;#32;json_value=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;flags,&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;json_value:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;value&amp;amp;#32;=&amp;amp;#32;(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;\n&amp;amp;quot;).encode()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(value)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;update_receipt(directory,&amp;amp;#32;receipt):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin-new.json&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(temporary,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.replace(temporary,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_root_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;Path(path).stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;administrative&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;group/other&amp;amp;#32;writable&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;receipt_for(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;find_prior(root,&amp;amp;#32;sha256):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;damaged&amp;amp;#32;journal&amp;amp;#32;must&amp;amp;#32;never&amp;amp;#32;silently&amp;amp;#32;allow&amp;amp;#32;repeated&amp;amp;#32;training.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;is&amp;amp;#32;incomplete;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;before&amp;amp;#32;more&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;==&amp;amp;#32;sha256&amp;amp;#32;and&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label=None,&amp;amp;#32;reason=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(root).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;collector.MIN_FREE_BYTES&amp;amp;#32;+&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;#32;to&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;reviewed&amp;amp;#32;snapshot&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;],&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;reviewed_utc&amp;amp;quot;:&amp;amp;#32;collector.utc_now(),&amp;amp;#32;&amp;amp;quot;status&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;preparing&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;selected_class&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;reason&amp;amp;quot;:&amp;amp;#32;reason}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;json.dumps(metadata,&amp;amp;#32;ensure_ascii=True).encode())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;complete&amp;amp;#32;original&amp;amp;#32;event.&amp;amp;#32;Never&amp;amp;#32;traverse&amp;amp;#32;or&amp;amp;#32;execute&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;source/.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id,&amp;amp;#32;archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;status&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;archive,&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;executable.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;path&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[str(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;rspamc&amp;amp;#32;reads&amp;amp;#32;the&amp;amp;#32;file&amp;amp;#32;itself;&amp;amp;#32;the&amp;amp;#32;secret&amp;amp;#32;never&amp;amp;#32;appears&amp;amp;#32;in&amp;amp;#32;argv&amp;amp;#32;or&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;learn_&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;-u/-d:&amp;amp;#32;one&amp;amp;#32;shared&amp;amp;#32;Bayes&amp;amp;#32;classifier.&amp;amp;#32;No&amp;amp;#32;shell&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;input=message,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;timeout=timeout,&amp;amp;#32;check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;learn_event(root,&amp;amp;#32;event_id,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;training&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;prior&amp;amp;#32;=&amp;amp;#32;find_prior(root,&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prior:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;prior&amp;amp;#32;learned&amp;amp;#32;or&amp;amp;#32;uncertain&amp;amp;#32;event;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;and&amp;amp;#32;reject&amp;amp;#32;the&amp;amp;#32;duplicate&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rc&amp;amp;#32;=&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;subprocess.TimeoutExpired:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;timeout;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;automatically&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_failed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;could&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;or&amp;amp;#32;local&amp;amp;#32;configuration&amp;amp;#32;was&amp;amp;#32;invalid&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;rc&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;rc&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;rc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;inspect&amp;amp;#32;server&amp;amp;#32;before&amp;amp;#32;any&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;reject_event(root,&amp;amp;#32;event_id,&amp;amp;#32;reason=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(reason)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;500:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;reason&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;rejected&amp;amp;quot;,&amp;amp;#32;reason=reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;list_events(root,&amp;amp;#32;state,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;((&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;(state,)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;directory.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;directory.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;notice&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;limit&amp;amp;#32;reached&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;limit&amp;amp;quot;:&amp;amp;#32;limit})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(entry)&amp;amp;#32;as&amp;amp;#32;item:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(item.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;metadata.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;id&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;state&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;review&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(row)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;state&amp;amp;quot;:&amp;amp;#32;name,&amp;amp;#32;&amp;amp;quot;error&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;#32;event;&amp;amp;#32;manual&amp;amp;#32;inspection&amp;amp;#32;required&amp;amp;quot;})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;show_event(root,&amp;amp;#32;event_id,&amp;amp;#32;state,&amp;amp;#32;path_only):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;state,&amp;amp;#32;check_id(event_id))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path_only:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;message_path&amp;amp;quot;:&amp;amp;#32;str(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;)})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;headers&amp;amp;#32;=&amp;amp;#32;BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message),&amp;amp;#32;262144)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;metadata&amp;amp;quot;:&amp;amp;#32;metadata,&amp;amp;#32;&amp;amp;quot;headers&amp;amp;quot;:&amp;amp;#32;{name:&amp;amp;#32;[str(v)[:2000]&amp;amp;#32;for&amp;amp;#32;v&amp;amp;#32;in&amp;amp;#32;headers.get_all(name,&amp;amp;#32;[])[:10]]&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;HEADERS}})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;review&amp;amp;quot;:&amp;amp;#32;receipt_for(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;warn_processed_storage(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;event&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event.name)&amp;amp;#32;or&amp;amp;#32;event.is_symlink()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;event.is_dir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;prefix&amp;amp;#32;in&amp;amp;#32;(event,&amp;amp;#32;event&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prefix.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(prefix&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_BYTES&amp;amp;#32;or&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;WARNING&amp;amp;#32;processed&amp;amp;#32;archive&amp;amp;#32;reached&amp;amp;#32;512&amp;amp;#32;MiB&amp;amp;#32;or&amp;amp;#32;1024&amp;amp;#32;events;&amp;amp;#32;review&amp;amp;#32;retention&amp;amp;#32;manually;&amp;amp;#32;nothing&amp;amp;#32;deleted&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sub&amp;amp;#32;=&amp;amp;#32;parser.add_subparsers(dest=&amp;amp;quot;command&amp;amp;quot;,&amp;amp;#32;required=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;list&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;JSON&amp;amp;#32;lines,&amp;amp;#32;metadata&amp;amp;#32;only&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--limit&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=100)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;show&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;selected&amp;amp;#32;headers&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;dumps&amp;amp;#32;the&amp;amp;#32;body&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--path&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;learn&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;explicitly&amp;amp;#32;train&amp;amp;#32;one&amp;amp;#32;reviewed&amp;amp;#32;event,&amp;amp;#32;then&amp;amp;#32;archive&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;help=&amp;amp;quot;root-only&amp;amp;#32;plaintext&amp;amp;#32;secret&amp;amp;#32;file;&amp;amp;#32;use&amp;amp;#32;--no-password&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;an&amp;amp;#32;intentionally&amp;amp;#32;trusted&amp;amp;#32;local&amp;amp;#32;controller&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--no-password&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;reject&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;archive&amp;amp;#32;one&amp;amp;#32;event&amp;amp;#32;without&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;deleting&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;--reason&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;root&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn_processed_storage(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;list&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.limit&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;10000:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;limit&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;1&amp;amp;#32;and&amp;amp;#32;10000&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;list_events(root,&amp;amp;#32;args.state,&amp;amp;#32;args.limit)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;show&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;show_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.state,&amp;amp;#32;args.path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;reject&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(reject_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.reason))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;timeout&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;0&amp;amp;#32;and&amp;amp;#32;300&amp;amp;#32;seconds&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;learn_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.label,&amp;amp;#32;executable=args.rspamc_bin,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;controller=args.controller,&amp;amp;#32;password_file=None&amp;amp;#32;if&amp;amp;#32;args.no_password&amp;amp;#32;else&amp;amp;#32;args.password_file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timeout=args.timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(result)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&amp;amp;#32;if&amp;amp;#32;result[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;operation&amp;amp;#32;refused&amp;amp;#32;or&amp;amp;#32;failed;&amp;amp;#32;inspect&amp;amp;#32;configuration/event&amp;amp;#32;locally&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;background&amp;amp;#32;learning&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;bounded&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback&amp;amp;#32;queue.&lt;br /&gt;
&lt;br /&gt;
The&amp;amp;#32;collector&amp;amp;#32;remains&amp;amp;#32;a&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;writer.&amp;amp;#32;This&amp;amp;#32;process&amp;amp;#32;owns&amp;amp;#32;controller&lt;br /&gt;
credentials,&amp;amp;#32;learns&amp;amp;#32;only&amp;amp;#32;validated&amp;amp;#32;INBOX/Junk&amp;amp;#32;events,&amp;amp;#32;preserves&amp;amp;#32;every&amp;amp;#32;original,&lt;br /&gt;
and&amp;amp;#32;never&amp;amp;#32;retries&amp;amp;#32;a&amp;amp;#32;training&amp;amp;#32;operation&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result.&amp;amp;#32;Run&amp;amp;#32;foreground&lt;br /&gt;
under&amp;amp;#32;FreeBSD&amp;amp;#32;daemon(8),&amp;amp;#32;or&amp;amp;#32;use&amp;amp;#32;--once&amp;amp;#32;for&amp;amp;#32;one&amp;amp;#32;bounded&amp;amp;#32;batch.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;signal&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;threading&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
&lt;br /&gt;
_spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_worker_admin&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback-admin.py&amp;amp;quot;))&lt;br /&gt;
admin&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;admin.FeedbackError&lt;br /&gt;
SHA_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{64}\Z&amp;amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
UNCERTAIN&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;log_event(kind,&amp;amp;#32;**fields):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Callers&amp;amp;#32;pass&amp;amp;#32;only&amp;amp;#32;fixed&amp;amp;#32;diagnostics,&amp;amp;#32;numeric&amp;amp;#32;counts,&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;classes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps({&amp;amp;quot;event&amp;amp;quot;:&amp;amp;#32;kind,&amp;amp;#32;**fields},&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True),&amp;amp;#32;flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_timestamp_ns(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(value,&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;missing&amp;amp;#32;timestamp&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parsed&amp;amp;#32;=&amp;amp;#32;datetime.fromisoformat(value.replace(&amp;amp;quot;Z&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;+00:00&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;parsed.tzinfo&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;timestamp&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;timezone&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;int(parsed.timestamp()&amp;amp;#32;*&amp;amp;#32;1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;order_events(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;bounded&amp;amp;#32;metadata,&amp;amp;#32;ordered&amp;amp;#32;by&amp;amp;#32;collector&amp;amp;#32;created_ns,&amp;amp;#32;then&amp;amp;#32;event&amp;amp;#32;ID.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;Legacy&amp;amp;#32;second-resolution&amp;amp;#32;timestamps&amp;amp;#32;are&amp;amp;#32;permitted&amp;amp;#32;only&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;same&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;contradictory&amp;amp;#32;labels&amp;amp;#32;at&amp;amp;#32;an&amp;amp;#32;identical&amp;amp;#32;timestamp.&amp;amp;#32;Such&amp;amp;#32;a&amp;amp;#32;tie&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cannot&amp;amp;#32;be&amp;amp;#32;resolved&amp;amp;#32;from&amp;amp;#32;random&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;is&amp;amp;#32;quarantined&amp;amp;#32;without&amp;amp;#32;learning.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pending&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows&amp;amp;#32;=&amp;amp;#32;[]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(pending)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;queue&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending&amp;amp;#32;/&amp;amp;#32;entry.name)&amp;amp;#32;as&amp;amp;#32;directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(directory.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;dict(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;sha256&amp;amp;quot;),&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(metadata[&amp;amp;quot;sha256&amp;amp;quot;]):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timestamp&amp;amp;#32;=&amp;amp;#32;metadata.get(&amp;amp;quot;created_ns&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;int)&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;bool)&amp;amp;#32;and&amp;amp;#32;timestamp&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;timestamp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;metadata:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(metadata.get(&amp;amp;quot;created_utc&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;manual&amp;amp;#32;administrator&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;archived&amp;amp;#32;this&amp;amp;#32;candidate.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;_invalid&amp;amp;quot;:&amp;amp;#32;True,&amp;amp;#32;&amp;amp;quot;_order_ns&amp;amp;quot;:&amp;amp;#32;0})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied&amp;amp;#32;=&amp;amp;#32;{}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied.setdefault((row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;]),&amp;amp;#32;set()).add(row[&amp;amp;quot;label&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;)&amp;amp;#32;and&amp;amp;#32;len(tied[(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;])])&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;sorted(rows,&amp;amp;#32;key=lambda&amp;amp;#32;row:&amp;amp;#32;(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;id&amp;amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_journal(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;root-owned&amp;amp;#32;receipts;&amp;amp;#32;successful&amp;amp;#32;classes&amp;amp;#32;use&amp;amp;#32;durable&amp;amp;#32;worker&amp;amp;#32;sequence.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked&amp;amp;#32;=&amp;amp;#32;{},&amp;amp;#32;set()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;used_bytes&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(processed)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;MAX_JOURNAL_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;processed&amp;amp;#32;/&amp;amp;#32;entry.name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.receipt_for(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(receipt,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;receipt.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;receipt&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(digest,&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(digest):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;worker_sequence&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(sequence,&amp;amp;#32;int)&amp;amp;#32;or&amp;amp;#32;isinstance(sequence,&amp;amp;#32;bool)&amp;amp;#32;or&amp;amp;#32;sequence&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;sequence&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;max(max_sequence,&amp;amp;#32;sequence)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;in&amp;amp;#32;UNCERTAIN:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;selected_class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;learned&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Existing&amp;amp;#32;manual&amp;amp;#32;receipts&amp;amp;#32;predate&amp;amp;#32;automatic&amp;amp;#32;processing;&amp;amp;#32;the&amp;amp;#32;old&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;admin&amp;amp;#32;refuses&amp;amp;#32;further&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;a&amp;amp;#32;SHA&amp;amp;#32;once&amp;amp;#32;it&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;receipt.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fallback&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(receipt.get(&amp;amp;quot;finished_utc&amp;amp;quot;,&amp;amp;#32;receipt.get(&amp;amp;quot;reviewed_utc&amp;amp;quot;)))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;position&amp;amp;#32;=&amp;amp;#32;(bool(sequence),&amp;amp;#32;sequence&amp;amp;#32;if&amp;amp;#32;sequence&amp;amp;#32;else&amp;amp;#32;fallback,&amp;amp;#32;entry.name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;or&amp;amp;#32;position&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;latest[digest][0]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;(position,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Count&amp;amp;#32;snapshots&amp;amp;#32;and&amp;amp;#32;preserved&amp;amp;#32;originals,&amp;amp;#32;never&amp;amp;#32;recursively&amp;amp;#32;walk&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;arbitrary&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail-user-owned&amp;amp;#32;source/&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;part&amp;amp;#32;in&amp;amp;#32;(directory,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(part)&amp;amp;#32;as&amp;amp;#32;safe:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;safe.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.stat(name,&amp;amp;#32;dir_fd=safe.fd,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(part&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_check_trusted_parent_chain(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;parent&amp;amp;#32;in&amp;amp;#32;Path(path).parents:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;link_info&amp;amp;#32;=&amp;amp;#32;parent.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;parent.stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(link_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;or&amp;amp;#32;info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;executable&amp;amp;#32;parent&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;controlled&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;resolve_executable(executable):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Allow&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;symlinks,&amp;amp;#32;but&amp;amp;#32;pin&amp;amp;#32;both&amp;amp;#32;operations&amp;amp;#32;to&amp;amp;#32;their&amp;amp;#32;target.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;source.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source_info&amp;amp;#32;=&amp;amp;#32;source.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;source_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;entry&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(source)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target&amp;amp;#32;=&amp;amp;#32;source.resolve(strict=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;target.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(target)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;controller_ready(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Authenticated&amp;amp;#32;read-only&amp;amp;#32;controller&amp;amp;#32;probe;&amp;amp;#32;no&amp;amp;#32;candidate&amp;amp;#32;is&amp;amp;#32;archived&amp;amp;#32;yet.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[resolve_executable(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unsafe&amp;amp;#32;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;stat&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;stdin=subprocess.DEVNULL,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;check=False,&amp;amp;#32;timeout=min(timeout,&amp;amp;#32;5)).returncode&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;process_pending(root,&amp;amp;#32;*,&amp;amp;#32;executable=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;,&amp;amp;#32;controller=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=admin.DEFAULT_PASSWORD_FILE,&amp;amp;#32;timeout=30,&amp;amp;#32;batch_size=20,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight=None,&amp;amp;#32;learner=None,&amp;amp;#32;stop_event=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Process&amp;amp;#32;at&amp;amp;#32;most&amp;amp;#32;batch_size&amp;amp;#32;candidates.&amp;amp;#32;Inject&amp;amp;#32;probes/learner&amp;amp;#32;for&amp;amp;#32;unit&amp;amp;#32;tests.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;One&amp;amp;#32;admin&amp;amp;#32;lock&amp;amp;#32;serializes&amp;amp;#32;manual&amp;amp;#32;and&amp;amp;#32;background&amp;amp;#32;operations.&amp;amp;#32;A&amp;amp;#32;crash&amp;amp;#32;after&amp;amp;#32;a&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;durable&amp;amp;#32;learning&amp;amp;#32;intent&amp;amp;#32;leaves&amp;amp;#32;status=learning,&amp;amp;#32;which&amp;amp;#32;blocks&amp;amp;#32;this&amp;amp;#32;SHA&amp;amp;#32;on&amp;amp;#32;the&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;next&amp;amp;#32;pass;&amp;amp;#32;subsequent&amp;amp;#32;candidates&amp;amp;#32;are&amp;amp;#32;preserved&amp;amp;#32;as&amp;amp;#32;quarantined_uncertain.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight&amp;amp;#32;=&amp;amp;#32;controller_ready&amp;amp;#32;if&amp;amp;#32;preflight&amp;amp;#32;is&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;preflight&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;learner&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;pkg&amp;amp;#32;installs&amp;amp;#32;rspamc&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;root-owned&amp;amp;#32;versioned&amp;amp;#32;symlink.&amp;amp;#32;Resolve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;it&amp;amp;#32;once&amp;amp;#32;so&amp;amp;#32;preflight&amp;amp;#32;and&amp;amp;#32;training&amp;amp;#32;invoke&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;trusted&amp;amp;#32;executable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;resolve_executable(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learner&amp;amp;#32;=&amp;amp;#32;admin.run_learning&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;learned&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;quot;:&amp;amp;#32;0,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;controller_unavailable&amp;amp;quot;:&amp;amp;#32;False,&amp;amp;#32;&amp;amp;quot;storage_blocked&amp;amp;quot;:&amp;amp;#32;False,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;archive_warning&amp;amp;quot;:&amp;amp;#32;False}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;events&amp;amp;#32;=&amp;amp;#32;order_events(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;len(events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;archive_count&amp;amp;#32;=&amp;amp;#32;_journal(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;preflight(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;candidate&amp;amp;#32;in&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;handled&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;batch_size&amp;amp;#32;or&amp;amp;#32;(stop_event&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;and&amp;amp;#32;stop_event.is_set()):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;candidate[&amp;amp;quot;id&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id).exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Crash&amp;amp;#32;before&amp;amp;#32;prepare_archive&amp;amp;#32;moved&amp;amp;#32;pending/ID:&amp;amp;#32;its&amp;amp;#32;root-owned&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;intent&amp;amp;#32;is&amp;amp;#32;authoritative.&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;pending&amp;amp;#32;original&amp;amp;#32;for&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;manual&amp;amp;#32;recovery&amp;amp;#32;and&amp;amp;#32;continue&amp;amp;#32;independent&amp;amp;#32;content,&amp;amp;#32;never&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;admin.read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;act&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;queued&amp;amp;#32;ordering/identity&amp;amp;#32;changed&amp;amp;#32;since&amp;amp;#32;enumeration.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;any(metadata.get(key)&amp;amp;#32;!=&amp;amp;#32;candidate.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(&amp;amp;quot;sha256&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;+&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label,&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;metadata[&amp;amp;quot;label&amp;amp;quot;],&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_ordering_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;event&amp;amp;#32;ordering&amp;amp;#32;is&amp;amp;#32;ambiguous&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;blocked:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;a&amp;amp;#32;prior&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;#32;has&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;and&amp;amp;#32;latest[digest][1]&amp;amp;#32;==&amp;amp;#32;label:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;same&amp;amp;#32;class&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;latest&amp;amp;#32;successful&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;automatic&amp;amp;#32;explicit&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label,&amp;amp;#32;reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;prepare_archive&amp;amp;#32;writes&amp;amp;#32;a&amp;amp;#32;root&amp;amp;#32;journal&amp;amp;#32;before&amp;amp;#32;moving&amp;amp;#32;the&amp;amp;#32;source;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;stop&amp;amp;#32;instead&amp;amp;#32;of&amp;amp;#32;retrying&amp;amp;#32;an&amp;amp;#32;incompletely&amp;amp;#32;prepared&amp;amp;#32;operation.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt.update({&amp;amp;quot;mode&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;automatic_imap_feedback&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;worker_sequence&amp;amp;quot;:&amp;amp;#32;sequence,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;processed_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&amp;amp;#32;&amp;amp;quot;source_created_ns&amp;amp;quot;:&amp;amp;#32;candidate[&amp;amp;quot;_order_ns&amp;amp;quot;]})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;previous_successful_class&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;latest[digest][1]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Durable&amp;amp;#32;intent&amp;amp;#32;exists&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;call&amp;amp;#32;that&amp;amp;#32;may&amp;amp;#32;change&amp;amp;#32;Bayes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;returncode&amp;amp;#32;=&amp;amp;#32;learner(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;failure;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;returncode&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;admin.collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;((True,&amp;amp;#32;sequence,&amp;amp;#32;event_id),&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;learned&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;duplicates&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive_count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;-=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;feedback_processed&amp;amp;quot;,&amp;amp;#32;id=event_id,&amp;amp;#32;label=label,&amp;amp;#32;status=receipt[&amp;amp;quot;status&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;controller&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;failed&amp;amp;#32;after&amp;amp;#32;the&amp;amp;#32;preflight.&amp;amp;#32;Leave&amp;amp;#32;all&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;other&amp;amp;#32;candidates&amp;amp;#32;pending&amp;amp;#32;until&amp;amp;#32;a&amp;amp;#32;fresh&amp;amp;#32;read-only&amp;amp;#32;probe&amp;amp;#32;passes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--poll&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--batch-size&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=20)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--once&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0.2&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.poll&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;60&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.batch_size&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;100:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;worker&amp;amp;#32;options&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping&amp;amp;#32;=&amp;amp;#32;threading.Event()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;signum&amp;amp;#32;in&amp;amp;#32;(signal.SIGTERM,&amp;amp;#32;signal.SIGINT):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;signal.signal(signum,&amp;amp;#32;lambda&amp;amp;#32;*_:&amp;amp;#32;stopping.set())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;lockfd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.worker.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(lockfd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;not&amp;amp;#32;stopping.is_set():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;process_pending(root,&amp;amp;#32;executable=args.rspamc_bin,&amp;amp;#32;controller=args.controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=args.password_file,&amp;amp;#32;timeout=args.timeout,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;batch_size=args.batch_size,&amp;amp;#32;stop_event=stopping)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;state&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;value&amp;amp;#32;for&amp;amp;#32;key,&amp;amp;#32;value&amp;amp;#32;in&amp;amp;#32;summary.items()&amp;amp;#32;if&amp;amp;#32;key&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;!=&amp;amp;#32;previous_state&amp;amp;#32;or&amp;amp;#32;any(summary[key]&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_state&amp;amp;quot;,&amp;amp;#32;**summary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;state&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;2&amp;amp;#32;if&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;or&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;else&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;previous_state&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_error&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;local&amp;amp;#32;queue,&amp;amp;#32;journal&amp;amp;#32;or&amp;amp;#32;configuration&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;#32;of&amp;amp;#32;uncertain&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping.wait(args.poll)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_start_failed&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;root,&amp;amp;#32;configuration&amp;amp;#32;or&amp;amp;#32;worker&amp;amp;#32;lock&amp;amp;#32;required&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(1)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;PROVIDE:&amp;amp;#32;rspamd_feedback&lt;br /&gt;
#&amp;amp;#32;REQUIRE:&amp;amp;#32;LOGIN&amp;amp;#32;redis&amp;amp;#32;rspamd&lt;br /&gt;
#&amp;amp;#32;KEYWORD:&amp;amp;#32;shutdown&lt;br /&gt;
#&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback,&amp;amp;#32;root:wheel&amp;amp;#32;0555.&lt;br /&gt;
#&amp;amp;#32;Enable&amp;amp;#32;with:&amp;amp;#32;sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
#&amp;amp;#32;Uses&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;daemon(8):&amp;amp;#32;rc.subr&amp;amp;#32;targets&amp;amp;#32;the&amp;amp;#32;supervisor&amp;amp;#32;PID&amp;amp;#32;(-P),&lt;br /&gt;
#&amp;amp;#32;not&amp;amp;#32;the&amp;amp;#32;child&amp;amp;#32;PID&amp;amp;#32;(-p),&amp;amp;#32;so&amp;amp;#32;stop&amp;amp;#32;cannot&amp;amp;#32;accidentally&amp;amp;#32;restart&amp;amp;#32;the&amp;amp;#32;worker.&lt;br /&gt;
&lt;br /&gt;
.&amp;amp;#32;/etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;amp;quot;rspamd_feedback&amp;amp;quot;&lt;br /&gt;
desc=&amp;amp;quot;Apply&amp;amp;#32;user&amp;amp;#32;spam/ham&amp;amp;#32;feedback&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;Bayes&amp;amp;#32;classifier&amp;amp;quot;&lt;br /&gt;
rcvar=&amp;amp;quot;rspamd_feedback_enable&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config&amp;amp;#32;&amp;amp;quot;${name}&amp;amp;quot;&lt;br /&gt;
:&amp;amp;#32;${rspamd_feedback_enable:=&amp;amp;quot;NO&amp;amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;amp;quot;/usr/sbin/daemon&amp;amp;quot;&lt;br /&gt;
procname=&amp;amp;quot;/usr/sbin/daemon&amp;amp;quot;&lt;br /&gt;
pidfile=&amp;amp;quot;/var/run/rspamd_feedback.pid&amp;amp;quot;&lt;br /&gt;
child_pidfile=&amp;amp;quot;/var/run/rspamd_feedback.child.pid&amp;amp;quot;&lt;br /&gt;
worker=&amp;amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;amp;quot;&lt;br /&gt;
python=&amp;amp;quot;/usr/local/bin/python3.12&amp;amp;quot;&lt;br /&gt;
logdir=&amp;amp;quot;/opt/mail/log/rspamd-feedback&amp;amp;quot;&lt;br /&gt;
logfile=&amp;amp;quot;${logdir}/worker.log&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;amp;quot;${python}&amp;amp;#32;${worker}&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;quot;&lt;br /&gt;
required_dirs=&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;#32;/opt/mail/rspamd-feedback/processed&amp;amp;#32;/opt/mail/rspamd&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Worker&amp;amp;#32;handles&amp;amp;#32;transient&amp;amp;#32;controller&amp;amp;#32;errors&amp;amp;#32;in&amp;amp;#32;its&amp;amp;#32;polling&amp;amp;#32;loop.&amp;amp;#32;Unexpected&lt;br /&gt;
#&amp;amp;#32;exits&amp;amp;#32;get&amp;amp;#32;at&amp;amp;#32;most&amp;amp;#32;five&amp;amp;#32;supervised&amp;amp;#32;restarts,&amp;amp;#32;spaced&amp;amp;#32;15&amp;amp;#32;seconds&amp;amp;#32;apart.&lt;br /&gt;
#&amp;amp;#32;-H&amp;amp;#32;reopens&amp;amp;#32;the&amp;amp;#32;log&amp;amp;#32;on&amp;amp;#32;SIGHUP&amp;amp;#32;without&amp;amp;#32;interrupting&amp;amp;#32;an&amp;amp;#32;in-flight&amp;amp;#32;learn&amp;amp;#32;request.&lt;br /&gt;
command_args=&amp;amp;quot;-c&amp;amp;#32;-f&amp;amp;#32;-H&amp;amp;#32;-R&amp;amp;#32;15&amp;amp;#32;-C&amp;amp;#32;5&amp;amp;#32;-P&amp;amp;#32;${pidfile}&amp;amp;#32;-p&amp;amp;#32;${child_pidfile}&amp;amp;#32;-t&amp;amp;#32;${name}&amp;amp;#32;-o&amp;amp;#32;${logfile}&amp;amp;#32;${python}&amp;amp;#32;-u&amp;amp;#32;${worker}&amp;amp;#32;--poll&amp;amp;#32;2&amp;amp;quot;&lt;br /&gt;
start_precmd=&amp;amp;quot;rspamd_feedback_prestart&amp;amp;quot;&lt;br /&gt;
extra_commands=&amp;amp;quot;reload&amp;amp;quot;&lt;br /&gt;
sig_reload=&amp;amp;quot;HUP&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;0&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn&amp;amp;#32;&amp;amp;quot;${name}&amp;amp;#32;requires&amp;amp;#32;root&amp;amp;#32;to&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;controller&amp;amp;#32;credential&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Refuse&amp;amp;#32;to&amp;amp;#32;write&amp;amp;#32;queue/log&amp;amp;#32;data&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;filesystem&amp;amp;#32;when&amp;amp;#32;/opt&amp;amp;#32;failed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;to&amp;amp;#32;mount.&amp;amp;#32;Directory&amp;amp;#32;existence&amp;amp;#32;alone&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;establish&amp;amp;#32;the&amp;amp;#32;mount&amp;amp;#32;state.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;!&amp;amp;#32;/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;/usr/bin/awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#x27;;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn&amp;amp;#32;&amp;amp;quot;${name}&amp;amp;#32;requires&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;mounted&amp;amp;#32;at&amp;amp;#32;/opt&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;${logdir}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;${logfile}&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn&amp;amp;#32;&amp;amp;quot;${name}&amp;amp;#32;refuses&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;at&amp;amp;#32;its&amp;amp;#32;private&amp;amp;#32;log&amp;amp;#32;path&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/usr/bin/install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;&amp;amp;quot;${logdir}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;${logfile}&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/usr/bin/install&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;&amp;amp;quot;${logfile}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/usr/sbin/chown&amp;amp;#32;root:wheel&amp;amp;#32;&amp;amp;quot;${logfile}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/bin/chmod&amp;amp;#32;0600&amp;amp;#32;&amp;amp;quot;${logfile}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;umask&amp;amp;#32;077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=675</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=675"/>
		<updated>2026-10-29T23:01:25Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* 15.6. Запустить автоматический обработчик */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;25&amp;amp;#32;/&amp;amp;#32;STARTTLS&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;465&amp;amp;#32;TLS&amp;amp;#32;или&amp;amp;#32;587&amp;amp;#32;STARTTLS&amp;amp;#32;+&amp;amp;#32;AUTH&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;LMTP&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;/opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;AUTH&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;файловый&amp;amp;#32;каталог&amp;amp;#32;пользователей&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;993&amp;amp;#32;TLS&amp;amp;#32;→&amp;amp;#32;Dovecot&lt;br /&gt;
SnappyMail&amp;amp;#32;(необязательно)&amp;amp;#32;→&amp;amp;#32;те&amp;amp;#32;же&amp;amp;#32;IMAPS&amp;amp;#32;и&amp;amp;#32;SMTP&amp;amp;#32;AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version&amp;amp;#32;-kru&lt;br /&gt;
uname&amp;amp;#32;-m&lt;br /&gt;
hostname&amp;amp;#32;-f&lt;br /&gt;
ifconfig&amp;amp;#32;-a&lt;br /&gt;
netstat&amp;amp;#32;-rn&lt;br /&gt;
cat&amp;amp;#32;/etc/resolv.conf&lt;br /&gt;
mount&amp;amp;#32;-p&lt;br /&gt;
df&amp;amp;#32;-hT&lt;br /&gt;
swapinfo&amp;amp;#32;-h&lt;br /&gt;
geom&amp;amp;#32;disk&amp;amp;#32;list&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-p&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-p&amp;amp;#32;da1&lt;br /&gt;
glabel&amp;amp;#32;status&lt;br /&gt;
fstyp&amp;amp;#32;/dev/da1&lt;br /&gt;
file&amp;amp;#32;-s&amp;amp;#32;/dev/da1&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
ls&amp;amp;#32;-ld&amp;amp;#32;/opt&lt;br /&gt;
cat&amp;amp;#32;/etc/fstab&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sysrc&amp;amp;#32;hostname=mail.example.org&lt;br /&gt;
hostname&amp;amp;#32;mail.example.org&lt;br /&gt;
sysrc&amp;amp;#32;defaultrouter=198.51.100.1&lt;br /&gt;
#&amp;amp;#32;Только&amp;amp;#32;если&amp;amp;#32;в&amp;amp;#32;работающей&amp;amp;#32;таблице&amp;amp;#32;отсутствует&amp;amp;#32;default&amp;amp;#32;route:&lt;br /&gt;
route&amp;amp;#32;add&amp;amp;#32;default&amp;amp;#32;198.51.100.1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo&amp;amp;#32;/dev/da1&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;{print&amp;amp;#32;$3}&#039;)&lt;br /&gt;
test&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;-gt&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;/dev/da1&amp;amp;#32;/dev/zero&lt;br /&gt;
printf&amp;amp;#32;&#039;cmp&amp;amp;#32;status:&amp;amp;#32;%s\n&#039;&amp;amp;#32;&amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;update&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
#&amp;amp;#32;После&amp;amp;#32;проверки&amp;amp;#32;плана:&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
sysrc&amp;amp;#32;exim_enable=NO&amp;amp;#32;dovecot_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_enable=NONE&amp;amp;#32;sendmail_submit_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_outbound_enable=NO&amp;amp;#32;sendmail_msp_queue_enable=NO&lt;br /&gt;
service&amp;amp;#32;sendmail&amp;amp;#32;onestop&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/dovecot&amp;amp;#32;--version&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;mailnull&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;mail&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;review&amp;amp;#32;and&amp;amp;#32;explicit&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback.&lt;br /&gt;
&lt;br /&gt;
No&amp;amp;#32;command&amp;amp;#32;trains&amp;amp;#32;automatically.&amp;amp;#32;list/show&amp;amp;#32;are&amp;amp;#32;local&amp;amp;#32;reads.&amp;amp;#32;learn&amp;amp;#32;ID&amp;amp;#32;spam|ham&lt;br /&gt;
is&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;command&amp;amp;#32;that&amp;amp;#32;invokes&amp;amp;#32;rspamc.&amp;amp;#32;reject&amp;amp;#32;ID&amp;amp;#32;archives&amp;amp;#32;without&amp;amp;#32;training.&lt;br /&gt;
Queue&amp;amp;#32;root&amp;amp;#32;and&amp;amp;#32;processed&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;non-writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users;&lt;br /&gt;
pending&amp;amp;#32;is&amp;amp;#32;separately&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;vmail,&amp;amp;#32;mode&amp;amp;#32;0700.&amp;amp;#32;Archived&amp;amp;#32;originals&amp;amp;#32;remain&amp;amp;#32;in&lt;br /&gt;
processed/ID/source;&amp;amp;#32;only&amp;amp;#32;root-owned&amp;amp;#32;snapshots&amp;amp;#32;are&amp;amp;#32;used&amp;amp;#32;for&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;review.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;email&amp;amp;#32;import&amp;amp;#32;policy&lt;br /&gt;
from&amp;amp;#32;email.parser&amp;amp;#32;import&amp;amp;#32;BytesHeaderParser&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
&lt;br /&gt;
spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_collector&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback.py&amp;amp;quot;))&lt;br /&gt;
collector&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;collector.FeedbackError&lt;br /&gt;
ID_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{32}\Z&amp;amp;quot;)&lt;br /&gt;
DEFAULT_ROOT&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback&amp;amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/usr/local/etc/rspamd/controller.password&amp;amp;quot;&lt;br /&gt;
HEADERS&amp;amp;#32;=&amp;amp;#32;(&amp;amp;quot;From&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;To&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Date&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Subject&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Message-ID&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Status&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Score&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Rspamd-Action&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;emit(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;require_root():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;hasattr(os,&amp;amp;#32;&amp;amp;quot;geteuid&amp;amp;quot;)&amp;amp;#32;or&amp;amp;#32;os.geteuid()&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;administrative&amp;amp;#32;command&amp;amp;#32;requires&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_id(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;ID&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;exactly&amp;amp;#32;32&amp;amp;#32;lowercase&amp;amp;#32;hexadecimal&amp;amp;#32;characters&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;Directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Nofollow&amp;amp;#32;directory&amp;amp;#32;descriptor&amp;amp;#32;on&amp;amp;#32;FreeBSD;&amp;amp;#32;portable&amp;amp;#32;fallback&amp;amp;#32;for&amp;amp;#32;local&amp;amp;#32;tests.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__init__(self,&amp;amp;#32;path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;self.path.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;expected&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;os.open(self.path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__enter__(self):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;self&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__exit__(self,&amp;amp;#32;*args):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;read(self,&amp;amp;#32;name,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;&amp;amp;quot;\\&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;.&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;..&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;internal&amp;amp;#32;filename&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(name,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW&amp;amp;#32;|&amp;amp;#32;os.O_NONBLOCK,&amp;amp;#32;dir_fd=self.fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;self.path&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;symlink&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;event&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;rb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;source:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.fstat(source.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;info.st_nlink&amp;amp;#32;!=&amp;amp;#32;1&amp;amp;#32;or&amp;amp;#32;info.st_size&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;bounded,&amp;amp;#32;single-link&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;source.read(limit&amp;amp;#32;+&amp;amp;#32;1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(result)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;exceeded&amp;amp;#32;the&amp;amp;#32;size&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;read_event(directory,&amp;amp;#32;event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;On&amp;amp;#32;POSIX&amp;amp;#32;open&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;relative&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;already&amp;amp;#32;opened&amp;amp;#32;parent&amp;amp;#32;descriptor.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;parent&amp;amp;#32;path&amp;amp;#32;is&amp;amp;#32;deployment-owned,&amp;amp;#32;and&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;event&amp;amp;#32;is&amp;amp;#32;rejected.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(Path(directory)&amp;amp;#32;/&amp;amp;#32;event_id)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;entry.read(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;MAX_MESSAGE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;user&amp;amp;quot;),&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;event_id&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;bytes&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;len(message)&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;hashlib.sha256(message).hexdigest():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;content&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;its&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata,&amp;amp;#32;message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write_private(path,&amp;amp;#32;value,&amp;amp;#32;*,&amp;amp;#32;json_value=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;flags,&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;json_value:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;value&amp;amp;#32;=&amp;amp;#32;(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;\n&amp;amp;quot;).encode()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(value)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;update_receipt(directory,&amp;amp;#32;receipt):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin-new.json&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(temporary,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.replace(temporary,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_root_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;Path(path).stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;administrative&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;group/other&amp;amp;#32;writable&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;receipt_for(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;find_prior(root,&amp;amp;#32;sha256):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;damaged&amp;amp;#32;journal&amp;amp;#32;must&amp;amp;#32;never&amp;amp;#32;silently&amp;amp;#32;allow&amp;amp;#32;repeated&amp;amp;#32;training.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;is&amp;amp;#32;incomplete;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;before&amp;amp;#32;more&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;==&amp;amp;#32;sha256&amp;amp;#32;and&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label=None,&amp;amp;#32;reason=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(root).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;collector.MIN_FREE_BYTES&amp;amp;#32;+&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;#32;to&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;reviewed&amp;amp;#32;snapshot&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;],&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;reviewed_utc&amp;amp;quot;:&amp;amp;#32;collector.utc_now(),&amp;amp;#32;&amp;amp;quot;status&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;preparing&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;selected_class&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;reason&amp;amp;quot;:&amp;amp;#32;reason}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;json.dumps(metadata,&amp;amp;#32;ensure_ascii=True).encode())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;complete&amp;amp;#32;original&amp;amp;#32;event.&amp;amp;#32;Never&amp;amp;#32;traverse&amp;amp;#32;or&amp;amp;#32;execute&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;source/.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id,&amp;amp;#32;archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;status&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;archive,&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;executable.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;path&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[str(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;rspamc&amp;amp;#32;reads&amp;amp;#32;the&amp;amp;#32;file&amp;amp;#32;itself;&amp;amp;#32;the&amp;amp;#32;secret&amp;amp;#32;never&amp;amp;#32;appears&amp;amp;#32;in&amp;amp;#32;argv&amp;amp;#32;or&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;learn_&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;-u/-d:&amp;amp;#32;one&amp;amp;#32;shared&amp;amp;#32;Bayes&amp;amp;#32;classifier.&amp;amp;#32;No&amp;amp;#32;shell&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;input=message,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;timeout=timeout,&amp;amp;#32;check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;learn_event(root,&amp;amp;#32;event_id,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;training&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;prior&amp;amp;#32;=&amp;amp;#32;find_prior(root,&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prior:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;prior&amp;amp;#32;learned&amp;amp;#32;or&amp;amp;#32;uncertain&amp;amp;#32;event;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;and&amp;amp;#32;reject&amp;amp;#32;the&amp;amp;#32;duplicate&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rc&amp;amp;#32;=&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;subprocess.TimeoutExpired:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;timeout;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;automatically&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_failed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;could&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;or&amp;amp;#32;local&amp;amp;#32;configuration&amp;amp;#32;was&amp;amp;#32;invalid&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;rc&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;rc&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;rc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;inspect&amp;amp;#32;server&amp;amp;#32;before&amp;amp;#32;any&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;reject_event(root,&amp;amp;#32;event_id,&amp;amp;#32;reason=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(reason)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;500:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;reason&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;rejected&amp;amp;quot;,&amp;amp;#32;reason=reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;list_events(root,&amp;amp;#32;state,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;((&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;(state,)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;directory.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;directory.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;notice&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;limit&amp;amp;#32;reached&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;limit&amp;amp;quot;:&amp;amp;#32;limit})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(entry)&amp;amp;#32;as&amp;amp;#32;item:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(item.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;metadata.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;id&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;state&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;review&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(row)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;state&amp;amp;quot;:&amp;amp;#32;name,&amp;amp;#32;&amp;amp;quot;error&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;#32;event;&amp;amp;#32;manual&amp;amp;#32;inspection&amp;amp;#32;required&amp;amp;quot;})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;show_event(root,&amp;amp;#32;event_id,&amp;amp;#32;state,&amp;amp;#32;path_only):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;state,&amp;amp;#32;check_id(event_id))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path_only:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;message_path&amp;amp;quot;:&amp;amp;#32;str(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;)})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;headers&amp;amp;#32;=&amp;amp;#32;BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message),&amp;amp;#32;262144)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;metadata&amp;amp;quot;:&amp;amp;#32;metadata,&amp;amp;#32;&amp;amp;quot;headers&amp;amp;quot;:&amp;amp;#32;{name:&amp;amp;#32;[str(v)[:2000]&amp;amp;#32;for&amp;amp;#32;v&amp;amp;#32;in&amp;amp;#32;headers.get_all(name,&amp;amp;#32;[])[:10]]&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;HEADERS}})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;review&amp;amp;quot;:&amp;amp;#32;receipt_for(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;warn_processed_storage(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;event&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event.name)&amp;amp;#32;or&amp;amp;#32;event.is_symlink()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;event.is_dir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;prefix&amp;amp;#32;in&amp;amp;#32;(event,&amp;amp;#32;event&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prefix.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(prefix&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_BYTES&amp;amp;#32;or&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;WARNING&amp;amp;#32;processed&amp;amp;#32;archive&amp;amp;#32;reached&amp;amp;#32;512&amp;amp;#32;MiB&amp;amp;#32;or&amp;amp;#32;1024&amp;amp;#32;events;&amp;amp;#32;review&amp;amp;#32;retention&amp;amp;#32;manually;&amp;amp;#32;nothing&amp;amp;#32;deleted&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sub&amp;amp;#32;=&amp;amp;#32;parser.add_subparsers(dest=&amp;amp;quot;command&amp;amp;quot;,&amp;amp;#32;required=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;list&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;JSON&amp;amp;#32;lines,&amp;amp;#32;metadata&amp;amp;#32;only&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--limit&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=100)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;show&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;selected&amp;amp;#32;headers&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;dumps&amp;amp;#32;the&amp;amp;#32;body&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--path&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;learn&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;explicitly&amp;amp;#32;train&amp;amp;#32;one&amp;amp;#32;reviewed&amp;amp;#32;event,&amp;amp;#32;then&amp;amp;#32;archive&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;help=&amp;amp;quot;root-only&amp;amp;#32;plaintext&amp;amp;#32;secret&amp;amp;#32;file;&amp;amp;#32;use&amp;amp;#32;--no-password&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;an&amp;amp;#32;intentionally&amp;amp;#32;trusted&amp;amp;#32;local&amp;amp;#32;controller&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--no-password&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;reject&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;archive&amp;amp;#32;one&amp;amp;#32;event&amp;amp;#32;without&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;deleting&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;--reason&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;root&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn_processed_storage(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;list&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.limit&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;10000:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;limit&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;1&amp;amp;#32;and&amp;amp;#32;10000&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;list_events(root,&amp;amp;#32;args.state,&amp;amp;#32;args.limit)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;show&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;show_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.state,&amp;amp;#32;args.path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;reject&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(reject_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.reason))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;timeout&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;0&amp;amp;#32;and&amp;amp;#32;300&amp;amp;#32;seconds&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;learn_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.label,&amp;amp;#32;executable=args.rspamc_bin,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;controller=args.controller,&amp;amp;#32;password_file=None&amp;amp;#32;if&amp;amp;#32;args.no_password&amp;amp;#32;else&amp;amp;#32;args.password_file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timeout=args.timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(result)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&amp;amp;#32;if&amp;amp;#32;result[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;operation&amp;amp;#32;refused&amp;amp;#32;or&amp;amp;#32;failed;&amp;amp;#32;inspect&amp;amp;#32;configuration/event&amp;amp;#32;locally&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;background&amp;amp;#32;learning&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;bounded&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback&amp;amp;#32;queue.&lt;br /&gt;
&lt;br /&gt;
The&amp;amp;#32;collector&amp;amp;#32;remains&amp;amp;#32;a&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;writer.&amp;amp;#32;This&amp;amp;#32;process&amp;amp;#32;owns&amp;amp;#32;controller&lt;br /&gt;
credentials,&amp;amp;#32;learns&amp;amp;#32;only&amp;amp;#32;validated&amp;amp;#32;INBOX/Junk&amp;amp;#32;events,&amp;amp;#32;preserves&amp;amp;#32;every&amp;amp;#32;original,&lt;br /&gt;
and&amp;amp;#32;never&amp;amp;#32;retries&amp;amp;#32;a&amp;amp;#32;training&amp;amp;#32;operation&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result.&amp;amp;#32;Run&amp;amp;#32;foreground&lt;br /&gt;
under&amp;amp;#32;FreeBSD&amp;amp;#32;daemon(8),&amp;amp;#32;or&amp;amp;#32;use&amp;amp;#32;--once&amp;amp;#32;for&amp;amp;#32;one&amp;amp;#32;bounded&amp;amp;#32;batch.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;signal&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;threading&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
&lt;br /&gt;
_spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_worker_admin&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback-admin.py&amp;amp;quot;))&lt;br /&gt;
admin&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;admin.FeedbackError&lt;br /&gt;
SHA_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{64}\Z&amp;amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
UNCERTAIN&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;log_event(kind,&amp;amp;#32;**fields):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Callers&amp;amp;#32;pass&amp;amp;#32;only&amp;amp;#32;fixed&amp;amp;#32;diagnostics,&amp;amp;#32;numeric&amp;amp;#32;counts,&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;classes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps({&amp;amp;quot;event&amp;amp;quot;:&amp;amp;#32;kind,&amp;amp;#32;**fields},&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True),&amp;amp;#32;flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_timestamp_ns(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(value,&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;missing&amp;amp;#32;timestamp&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parsed&amp;amp;#32;=&amp;amp;#32;datetime.fromisoformat(value.replace(&amp;amp;quot;Z&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;+00:00&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;parsed.tzinfo&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;timestamp&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;timezone&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;int(parsed.timestamp()&amp;amp;#32;*&amp;amp;#32;1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;order_events(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;bounded&amp;amp;#32;metadata,&amp;amp;#32;ordered&amp;amp;#32;by&amp;amp;#32;collector&amp;amp;#32;created_ns,&amp;amp;#32;then&amp;amp;#32;event&amp;amp;#32;ID.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;Legacy&amp;amp;#32;second-resolution&amp;amp;#32;timestamps&amp;amp;#32;are&amp;amp;#32;permitted&amp;amp;#32;only&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;same&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;contradictory&amp;amp;#32;labels&amp;amp;#32;at&amp;amp;#32;an&amp;amp;#32;identical&amp;amp;#32;timestamp.&amp;amp;#32;Such&amp;amp;#32;a&amp;amp;#32;tie&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cannot&amp;amp;#32;be&amp;amp;#32;resolved&amp;amp;#32;from&amp;amp;#32;random&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;is&amp;amp;#32;quarantined&amp;amp;#32;without&amp;amp;#32;learning.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pending&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows&amp;amp;#32;=&amp;amp;#32;[]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(pending)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;queue&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending&amp;amp;#32;/&amp;amp;#32;entry.name)&amp;amp;#32;as&amp;amp;#32;directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(directory.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;dict(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;sha256&amp;amp;quot;),&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(metadata[&amp;amp;quot;sha256&amp;amp;quot;]):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timestamp&amp;amp;#32;=&amp;amp;#32;metadata.get(&amp;amp;quot;created_ns&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;int)&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;bool)&amp;amp;#32;and&amp;amp;#32;timestamp&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;timestamp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;metadata:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(metadata.get(&amp;amp;quot;created_utc&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;manual&amp;amp;#32;administrator&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;archived&amp;amp;#32;this&amp;amp;#32;candidate.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;_invalid&amp;amp;quot;:&amp;amp;#32;True,&amp;amp;#32;&amp;amp;quot;_order_ns&amp;amp;quot;:&amp;amp;#32;0})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied&amp;amp;#32;=&amp;amp;#32;{}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied.setdefault((row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;]),&amp;amp;#32;set()).add(row[&amp;amp;quot;label&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;)&amp;amp;#32;and&amp;amp;#32;len(tied[(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;])])&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;sorted(rows,&amp;amp;#32;key=lambda&amp;amp;#32;row:&amp;amp;#32;(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;id&amp;amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_journal(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;root-owned&amp;amp;#32;receipts;&amp;amp;#32;successful&amp;amp;#32;classes&amp;amp;#32;use&amp;amp;#32;durable&amp;amp;#32;worker&amp;amp;#32;sequence.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked&amp;amp;#32;=&amp;amp;#32;{},&amp;amp;#32;set()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;used_bytes&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(processed)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;MAX_JOURNAL_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;processed&amp;amp;#32;/&amp;amp;#32;entry.name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.receipt_for(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(receipt,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;receipt.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;receipt&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(digest,&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(digest):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;worker_sequence&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(sequence,&amp;amp;#32;int)&amp;amp;#32;or&amp;amp;#32;isinstance(sequence,&amp;amp;#32;bool)&amp;amp;#32;or&amp;amp;#32;sequence&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;sequence&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;max(max_sequence,&amp;amp;#32;sequence)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;in&amp;amp;#32;UNCERTAIN:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;selected_class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;learned&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Existing&amp;amp;#32;manual&amp;amp;#32;receipts&amp;amp;#32;predate&amp;amp;#32;automatic&amp;amp;#32;processing;&amp;amp;#32;the&amp;amp;#32;old&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;admin&amp;amp;#32;refuses&amp;amp;#32;further&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;a&amp;amp;#32;SHA&amp;amp;#32;once&amp;amp;#32;it&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;receipt.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fallback&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(receipt.get(&amp;amp;quot;finished_utc&amp;amp;quot;,&amp;amp;#32;receipt.get(&amp;amp;quot;reviewed_utc&amp;amp;quot;)))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;position&amp;amp;#32;=&amp;amp;#32;(bool(sequence),&amp;amp;#32;sequence&amp;amp;#32;if&amp;amp;#32;sequence&amp;amp;#32;else&amp;amp;#32;fallback,&amp;amp;#32;entry.name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;or&amp;amp;#32;position&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;latest[digest][0]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;(position,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Count&amp;amp;#32;snapshots&amp;amp;#32;and&amp;amp;#32;preserved&amp;amp;#32;originals,&amp;amp;#32;never&amp;amp;#32;recursively&amp;amp;#32;walk&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;arbitrary&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail-user-owned&amp;amp;#32;source/&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;part&amp;amp;#32;in&amp;amp;#32;(directory,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(part)&amp;amp;#32;as&amp;amp;#32;safe:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;safe.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.stat(name,&amp;amp;#32;dir_fd=safe.fd,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(part&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_check_trusted_parent_chain(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;parent&amp;amp;#32;in&amp;amp;#32;Path(path).parents:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;link_info&amp;amp;#32;=&amp;amp;#32;parent.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;parent.stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(link_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;or&amp;amp;#32;info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;executable&amp;amp;#32;parent&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;controlled&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;resolve_executable(executable):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Allow&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;symlinks,&amp;amp;#32;but&amp;amp;#32;pin&amp;amp;#32;both&amp;amp;#32;operations&amp;amp;#32;to&amp;amp;#32;their&amp;amp;#32;target.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;source.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source_info&amp;amp;#32;=&amp;amp;#32;source.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;source_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;entry&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(source)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target&amp;amp;#32;=&amp;amp;#32;source.resolve(strict=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;target.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(target)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;controller_ready(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Authenticated&amp;amp;#32;read-only&amp;amp;#32;controller&amp;amp;#32;probe;&amp;amp;#32;no&amp;amp;#32;candidate&amp;amp;#32;is&amp;amp;#32;archived&amp;amp;#32;yet.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[resolve_executable(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unsafe&amp;amp;#32;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;stat&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;stdin=subprocess.DEVNULL,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;check=False,&amp;amp;#32;timeout=min(timeout,&amp;amp;#32;5)).returncode&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;process_pending(root,&amp;amp;#32;*,&amp;amp;#32;executable=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;,&amp;amp;#32;controller=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=admin.DEFAULT_PASSWORD_FILE,&amp;amp;#32;timeout=30,&amp;amp;#32;batch_size=20,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight=None,&amp;amp;#32;learner=None,&amp;amp;#32;stop_event=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Process&amp;amp;#32;at&amp;amp;#32;most&amp;amp;#32;batch_size&amp;amp;#32;candidates.&amp;amp;#32;Inject&amp;amp;#32;probes/learner&amp;amp;#32;for&amp;amp;#32;unit&amp;amp;#32;tests.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;One&amp;amp;#32;admin&amp;amp;#32;lock&amp;amp;#32;serializes&amp;amp;#32;manual&amp;amp;#32;and&amp;amp;#32;background&amp;amp;#32;operations.&amp;amp;#32;A&amp;amp;#32;crash&amp;amp;#32;after&amp;amp;#32;a&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;durable&amp;amp;#32;learning&amp;amp;#32;intent&amp;amp;#32;leaves&amp;amp;#32;status=learning,&amp;amp;#32;which&amp;amp;#32;blocks&amp;amp;#32;this&amp;amp;#32;SHA&amp;amp;#32;on&amp;amp;#32;the&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;next&amp;amp;#32;pass;&amp;amp;#32;subsequent&amp;amp;#32;candidates&amp;amp;#32;are&amp;amp;#32;preserved&amp;amp;#32;as&amp;amp;#32;quarantined_uncertain.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight&amp;amp;#32;=&amp;amp;#32;controller_ready&amp;amp;#32;if&amp;amp;#32;preflight&amp;amp;#32;is&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;preflight&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;learner&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;pkg&amp;amp;#32;installs&amp;amp;#32;rspamc&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;root-owned&amp;amp;#32;versioned&amp;amp;#32;symlink.&amp;amp;#32;Resolve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;it&amp;amp;#32;once&amp;amp;#32;so&amp;amp;#32;preflight&amp;amp;#32;and&amp;amp;#32;training&amp;amp;#32;invoke&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;trusted&amp;amp;#32;executable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;resolve_executable(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learner&amp;amp;#32;=&amp;amp;#32;admin.run_learning&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;learned&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;quot;:&amp;amp;#32;0,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;controller_unavailable&amp;amp;quot;:&amp;amp;#32;False,&amp;amp;#32;&amp;amp;quot;storage_blocked&amp;amp;quot;:&amp;amp;#32;False,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;archive_warning&amp;amp;quot;:&amp;amp;#32;False}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;events&amp;amp;#32;=&amp;amp;#32;order_events(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;len(events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;archive_count&amp;amp;#32;=&amp;amp;#32;_journal(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;preflight(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;candidate&amp;amp;#32;in&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;handled&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;batch_size&amp;amp;#32;or&amp;amp;#32;(stop_event&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;and&amp;amp;#32;stop_event.is_set()):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;candidate[&amp;amp;quot;id&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id).exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Crash&amp;amp;#32;before&amp;amp;#32;prepare_archive&amp;amp;#32;moved&amp;amp;#32;pending/ID:&amp;amp;#32;its&amp;amp;#32;root-owned&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;intent&amp;amp;#32;is&amp;amp;#32;authoritative.&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;pending&amp;amp;#32;original&amp;amp;#32;for&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;manual&amp;amp;#32;recovery&amp;amp;#32;and&amp;amp;#32;continue&amp;amp;#32;independent&amp;amp;#32;content,&amp;amp;#32;never&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;admin.read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;act&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;queued&amp;amp;#32;ordering/identity&amp;amp;#32;changed&amp;amp;#32;since&amp;amp;#32;enumeration.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;any(metadata.get(key)&amp;amp;#32;!=&amp;amp;#32;candidate.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(&amp;amp;quot;sha256&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;+&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label,&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;metadata[&amp;amp;quot;label&amp;amp;quot;],&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_ordering_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;event&amp;amp;#32;ordering&amp;amp;#32;is&amp;amp;#32;ambiguous&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;blocked:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;a&amp;amp;#32;prior&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;#32;has&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;and&amp;amp;#32;latest[digest][1]&amp;amp;#32;==&amp;amp;#32;label:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;same&amp;amp;#32;class&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;latest&amp;amp;#32;successful&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;automatic&amp;amp;#32;explicit&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label,&amp;amp;#32;reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;prepare_archive&amp;amp;#32;writes&amp;amp;#32;a&amp;amp;#32;root&amp;amp;#32;journal&amp;amp;#32;before&amp;amp;#32;moving&amp;amp;#32;the&amp;amp;#32;source;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;stop&amp;amp;#32;instead&amp;amp;#32;of&amp;amp;#32;retrying&amp;amp;#32;an&amp;amp;#32;incompletely&amp;amp;#32;prepared&amp;amp;#32;operation.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt.update({&amp;amp;quot;mode&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;automatic_imap_feedback&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;worker_sequence&amp;amp;quot;:&amp;amp;#32;sequence,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;processed_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&amp;amp;#32;&amp;amp;quot;source_created_ns&amp;amp;quot;:&amp;amp;#32;candidate[&amp;amp;quot;_order_ns&amp;amp;quot;]})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;previous_successful_class&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;latest[digest][1]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Durable&amp;amp;#32;intent&amp;amp;#32;exists&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;call&amp;amp;#32;that&amp;amp;#32;may&amp;amp;#32;change&amp;amp;#32;Bayes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;returncode&amp;amp;#32;=&amp;amp;#32;learner(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;failure;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;returncode&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;admin.collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;((True,&amp;amp;#32;sequence,&amp;amp;#32;event_id),&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;learned&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;duplicates&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive_count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;-=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;feedback_processed&amp;amp;quot;,&amp;amp;#32;id=event_id,&amp;amp;#32;label=label,&amp;amp;#32;status=receipt[&amp;amp;quot;status&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;controller&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;failed&amp;amp;#32;after&amp;amp;#32;the&amp;amp;#32;preflight.&amp;amp;#32;Leave&amp;amp;#32;all&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;other&amp;amp;#32;candidates&amp;amp;#32;pending&amp;amp;#32;until&amp;amp;#32;a&amp;amp;#32;fresh&amp;amp;#32;read-only&amp;amp;#32;probe&amp;amp;#32;passes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--poll&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--batch-size&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=20)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--once&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0.2&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.poll&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;60&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.batch_size&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;100:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;worker&amp;amp;#32;options&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping&amp;amp;#32;=&amp;amp;#32;threading.Event()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;signum&amp;amp;#32;in&amp;amp;#32;(signal.SIGTERM,&amp;amp;#32;signal.SIGINT):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;signal.signal(signum,&amp;amp;#32;lambda&amp;amp;#32;*_:&amp;amp;#32;stopping.set())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;lockfd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.worker.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(lockfd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;not&amp;amp;#32;stopping.is_set():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;process_pending(root,&amp;amp;#32;executable=args.rspamc_bin,&amp;amp;#32;controller=args.controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=args.password_file,&amp;amp;#32;timeout=args.timeout,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;batch_size=args.batch_size,&amp;amp;#32;stop_event=stopping)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;state&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;value&amp;amp;#32;for&amp;amp;#32;key,&amp;amp;#32;value&amp;amp;#32;in&amp;amp;#32;summary.items()&amp;amp;#32;if&amp;amp;#32;key&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;!=&amp;amp;#32;previous_state&amp;amp;#32;or&amp;amp;#32;any(summary[key]&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_state&amp;amp;quot;,&amp;amp;#32;**summary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;state&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;2&amp;amp;#32;if&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;or&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;else&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;previous_state&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_error&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;local&amp;amp;#32;queue,&amp;amp;#32;journal&amp;amp;#32;or&amp;amp;#32;configuration&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;#32;of&amp;amp;#32;uncertain&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping.wait(args.poll)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_start_failed&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;root,&amp;amp;#32;configuration&amp;amp;#32;or&amp;amp;#32;worker&amp;amp;#32;lock&amp;amp;#32;required&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(1)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;PROVIDE:&amp;amp;#32;rspamd_feedback&lt;br /&gt;
#&amp;amp;#32;REQUIRE:&amp;amp;#32;LOGIN&amp;amp;#32;redis&amp;amp;#32;rspamd&lt;br /&gt;
#&amp;amp;#32;KEYWORD:&amp;amp;#32;shutdown&lt;br /&gt;
#&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback,&amp;amp;#32;root:wheel&amp;amp;#32;0555.&lt;br /&gt;
#&amp;amp;#32;Enable&amp;amp;#32;with:&amp;amp;#32;sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
#&amp;amp;#32;Uses&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;daemon(8):&amp;amp;#32;rc.subr&amp;amp;#32;targets&amp;amp;#32;the&amp;amp;#32;supervisor&amp;amp;#32;PID&amp;amp;#32;(-P),&lt;br /&gt;
#&amp;amp;#32;not&amp;amp;#32;the&amp;amp;#32;child&amp;amp;#32;PID&amp;amp;#32;(-p),&amp;amp;#32;so&amp;amp;#32;stop&amp;amp;#32;cannot&amp;amp;#32;accidentally&amp;amp;#32;restart&amp;amp;#32;the&amp;amp;#32;worker.&lt;br /&gt;
&lt;br /&gt;
.&amp;amp;#32;/etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;amp;quot;rspamd_feedback&amp;amp;quot;&lt;br /&gt;
desc=&amp;amp;quot;Apply&amp;amp;#32;user&amp;amp;#32;spam/ham&amp;amp;#32;feedback&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;Bayes&amp;amp;#32;classifier&amp;amp;quot;&lt;br /&gt;
rcvar=&amp;amp;quot;rspamd_feedback_enable&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config&amp;amp;#32;&amp;amp;quot;${name}&amp;amp;quot;&lt;br /&gt;
:&amp;amp;#32;${rspamd_feedback_enable:=&amp;amp;quot;NO&amp;amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;amp;quot;/usr/sbin/daemon&amp;amp;quot;&lt;br /&gt;
procname=&amp;amp;quot;/usr/sbin/daemon&amp;amp;quot;&lt;br /&gt;
pidfile=&amp;amp;quot;/var/run/rspamd_feedback.pid&amp;amp;quot;&lt;br /&gt;
child_pidfile=&amp;amp;quot;/var/run/rspamd_feedback.child.pid&amp;amp;quot;&lt;br /&gt;
worker=&amp;amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;amp;quot;&lt;br /&gt;
python=&amp;amp;quot;/usr/local/bin/python3.12&amp;amp;quot;&lt;br /&gt;
logdir=&amp;amp;quot;/opt/mail/log/rspamd-feedback&amp;amp;quot;&lt;br /&gt;
logfile=&amp;amp;quot;${logdir}/worker.log&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;amp;quot;${python}&amp;amp;#32;${worker}&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;quot;&lt;br /&gt;
required_dirs=&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;#32;/opt/mail/rspamd-feedback/processed&amp;amp;#32;/opt/mail/rspamd&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Worker&amp;amp;#32;handles&amp;amp;#32;transient&amp;amp;#32;controller&amp;amp;#32;errors&amp;amp;#32;in&amp;amp;#32;its&amp;amp;#32;polling&amp;amp;#32;loop.&amp;amp;#32;Unexpected&lt;br /&gt;
#&amp;amp;#32;exits&amp;amp;#32;get&amp;amp;#32;at&amp;amp;#32;most&amp;amp;#32;five&amp;amp;#32;supervised&amp;amp;#32;restarts,&amp;amp;#32;spaced&amp;amp;#32;15&amp;amp;#32;seconds&amp;amp;#32;apart.&lt;br /&gt;
#&amp;amp;#32;-H&amp;amp;#32;reopens&amp;amp;#32;the&amp;amp;#32;log&amp;amp;#32;on&amp;amp;#32;SIGHUP&amp;amp;#32;without&amp;amp;#32;interrupting&amp;amp;#32;an&amp;amp;#32;in-flight&amp;amp;#32;learn&amp;amp;#32;request.&lt;br /&gt;
command_args=&amp;amp;quot;-c&amp;amp;#32;-f&amp;amp;#32;-H&amp;amp;#32;-R&amp;amp;#32;15&amp;amp;#32;-C&amp;amp;#32;5&amp;amp;#32;-P&amp;amp;#32;${pidfile}&amp;amp;#32;-p&amp;amp;#32;${child_pidfile}&amp;amp;#32;-t&amp;amp;#32;${name}&amp;amp;#32;-o&amp;amp;#32;${logfile}&amp;amp;#32;${python}&amp;amp;#32;-u&amp;amp;#32;${worker}&amp;amp;#32;--poll&amp;amp;#32;2&amp;amp;quot;&lt;br /&gt;
start_precmd=&amp;amp;quot;rspamd_feedback_prestart&amp;amp;quot;&lt;br /&gt;
extra_commands=&amp;amp;quot;reload&amp;amp;quot;&lt;br /&gt;
sig_reload=&amp;amp;quot;HUP&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;0&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn&amp;amp;#32;&amp;amp;quot;${name}&amp;amp;#32;requires&amp;amp;#32;root&amp;amp;#32;to&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;controller&amp;amp;#32;credential&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Refuse&amp;amp;#32;to&amp;amp;#32;write&amp;amp;#32;queue/log&amp;amp;#32;data&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;filesystem&amp;amp;#32;when&amp;amp;#32;/opt&amp;amp;#32;failed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;to&amp;amp;#32;mount.&amp;amp;#32;Directory&amp;amp;#32;existence&amp;amp;#32;alone&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;establish&amp;amp;#32;the&amp;amp;#32;mount&amp;amp;#32;state.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;!&amp;amp;#32;/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;/usr/bin/awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#x27;;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn&amp;amp;#32;&amp;amp;quot;${name}&amp;amp;#32;requires&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;mounted&amp;amp;#32;at&amp;amp;#32;/opt&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;${logdir}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;${logfile}&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn&amp;amp;#32;&amp;amp;quot;${name}&amp;amp;#32;refuses&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;at&amp;amp;#32;its&amp;amp;#32;private&amp;amp;#32;log&amp;amp;#32;path&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/usr/bin/install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;&amp;amp;quot;${logdir}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;${logfile}&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/usr/bin/install&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;&amp;amp;quot;${logfile}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/usr/sbin/chown&amp;amp;#32;root:wheel&amp;amp;#32;&amp;amp;quot;${logfile}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/bin/chmod&amp;amp;#32;0600&amp;amp;#32;&amp;amp;quot;${logfile}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;umask&amp;amp;#32;077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=Test&amp;diff=674</id>
		<title>Test</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=Test&amp;diff=674"/>
		<updated>2026-10-29T22:29:28Z</updated>

		<summary type="html">&lt;p&gt;Bas: Полностью удалено содержимое страницы&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=Test&amp;diff=673</id>
		<title>Test</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=Test&amp;diff=673"/>
		<updated>2026-10-29T22:29:04Z</updated>

		<summary type="html">&lt;p&gt;Bas: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;indicator&amp;gt;&lt;br /&gt;
qwerty123&lt;br /&gt;
asdf456&lt;br /&gt;
zxcv789&lt;br /&gt;
&amp;lt;/indicator&amp;gt;&lt;br /&gt;
&amp;lt;langconvert&amp;gt;&lt;br /&gt;
qwerty123&lt;br /&gt;
asdf456&lt;br /&gt;
zxcv789&lt;br /&gt;
&amp;lt;/langconvert&amp;gt;&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=Test&amp;diff=672</id>
		<title>Test</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=Test&amp;diff=672"/>
		<updated>2026-10-29T22:19:55Z</updated>

		<summary type="html">&lt;p&gt;Bas: Новая страница: «&amp;lt;source lang=&amp;quot;test&amp;quot; line&amp;gt; qwerty123 asdf456 zxcv789 &amp;lt;/source&amp;gt;»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;source lang=&amp;quot;test&amp;quot; line&amp;gt;&lt;br /&gt;
qwerty123&lt;br /&gt;
asdf456&lt;br /&gt;
zxcv789&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0&amp;diff=671</id>
		<title>Заглавная страница</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0&amp;diff=671"/>
		<updated>2026-10-29T22:17:23Z</updated>

		<summary type="html">&lt;p&gt;Bas: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Реклама:&lt;br /&gt;
* Если у Вас есть вопросы или Вам необходима настройка и сопровождение вашего сервера и/или сервисов на базе Linux или FreeBSD, то Вы можете связаться со мной по адресу bas@gwtools.ru&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Список категорий:&lt;br /&gt;
* [[Служебная:Категории|Категории]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Список статей:&lt;br /&gt;
* [[Автодополнение в командной строке]]&lt;br /&gt;
* [[Базовая настройка FreeBSD]]&lt;br /&gt;
* [[Выпуск SSL-сертификата]]&lt;br /&gt;
* [[Вытаскивание файлов из кеша Squid&#039;а]]&lt;br /&gt;
* [[Давай я за тебя погуглю]]&lt;br /&gt;
* [[Добавление пользователя для удалённого доступа в MySQL]]&lt;br /&gt;
* [[Добавление местонахождения в vBulletin на странице online.php]]&lt;br /&gt;
* [[Изменение главного меню в Zabbix]]&lt;br /&gt;
* [[Кеширование компиляции с помощью ccache]]&lt;br /&gt;
* [[Монтирование glusterfs-разделов при загрузке системы]]&lt;br /&gt;
* [[Монтирование ISO-образа]]&lt;br /&gt;
* [[На Toshiba Sattelite перестали работать сенсорные кнопки]]&lt;br /&gt;
* [[Настройка OpenVPN на FreeBSD]]&lt;br /&gt;
* [[Настройка внешней авторизации в Apache]]&lt;br /&gt;
* [[Настройка мониторинга MySQL с помощью Zabbix]]&lt;br /&gt;
* [[Настройка ограничений на отправку в Exim (ratelimit)]]&lt;br /&gt;
* [[Обновление часового пояса на FreeBSD]]&lt;br /&gt;
* [[Ограничение доступа к Squid (по адресу, по паролю, по времени)]]&lt;br /&gt;
* [[Отказ от SSLv2 и SSLv3 с уведомлением пользователей]]&lt;br /&gt;
* [[Отключение правил форума при регистрации]]&lt;br /&gt;
* [[Отключение формы обратной связи‎‎]]&lt;br /&gt;
* [[Отправка файла по почте через консоль]]&lt;br /&gt;
* [[Поиск во FreeBSD]]&lt;br /&gt;
* [[Поиск установленных соединений]]&lt;br /&gt;
* [[Почтовый сервер на FreeBSD 15.1: Exim и Dovecot]]&lt;br /&gt;
* [[Правильный перезапуск сети и маршрутизации во FreeBSD]]&lt;br /&gt;
* [[Проверка часового пояса]]&lt;br /&gt;
* [[Репликация MySQL]]&lt;br /&gt;
* [[Создание раздела более 2 ТБ]]&lt;br /&gt;
* [[Создание раздела с помощью parted]]&lt;br /&gt;
* [[Тестирование скорости чтения и записи жёсткого диска]]&lt;br /&gt;
* [[Удаление ^M]]&lt;br /&gt;
* [[Удаление Powered by vBulletin из заголовка страницы]]&lt;br /&gt;
* [[Управление пользователями с помощью bash-скриптов]]&lt;br /&gt;
* [[Управление разделами с помощью gpart]]&lt;br /&gt;
* [[Установка Bareos на FreeBSD]]&lt;br /&gt;
* [[Установка и настройка git на FreeBSD (freebsd+nginx+fcgiwrap+git+gitweb)]]&lt;br /&gt;
* [[Установка zabbix-agent на SUSE Linux Enterprise Server]]&lt;br /&gt;
* [[error_reporting и вывод на экран]]&lt;br /&gt;
* [[.htaccess и mod rewrite в nginx или как я мигрировал с Apache]]&lt;br /&gt;
* [[Модули для Apache - мой мини-комплект]]&lt;br /&gt;
* [[Apache: Couldn&#039;t create accept lock]]&lt;br /&gt;
* [[Out of inodes]]&lt;br /&gt;
* [[RHEL обновление лицензии]]&lt;br /&gt;
* [[Toshiba, обновление BIOS, проблемы с яркостью экрана, регулятором громкости]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0&amp;diff=670</id>
		<title>Заглавная страница</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0&amp;diff=670"/>
		<updated>2026-10-29T22:07:34Z</updated>

		<summary type="html">&lt;p&gt;Bas: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Реклама:&lt;br /&gt;
* Если у Вас есть вопросы или Вам необходима настройка и сопровождение вашего сервера и/или сервисов на базе Linux или FreeBSD, то Вы можете связаться со мной по адресу bas@gwtools.ru&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Список категорий:&lt;br /&gt;
* [[Служебная:Категории|Категории]]&lt;br /&gt;
* [[Special:SpecialPages|Все служебные страницы]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Список статей:&lt;br /&gt;
* [[Автодополнение в командной строке]]&lt;br /&gt;
* [[Базовая настройка FreeBSD]]&lt;br /&gt;
* [[Выпуск SSL-сертификата]]&lt;br /&gt;
* [[Вытаскивание файлов из кеша Squid&#039;а]]&lt;br /&gt;
* [[Давай я за тебя погуглю]]&lt;br /&gt;
* [[Добавление пользователя для удалённого доступа в MySQL]]&lt;br /&gt;
* [[Добавление местонахождения в vBulletin на странице online.php]]&lt;br /&gt;
* [[Изменение главного меню в Zabbix]]&lt;br /&gt;
* [[Кеширование компиляции с помощью ccache]]&lt;br /&gt;
* [[Монтирование glusterfs-разделов при загрузке системы]]&lt;br /&gt;
* [[Монтирование ISO-образа]]&lt;br /&gt;
* [[На Toshiba Sattelite перестали работать сенсорные кнопки]]&lt;br /&gt;
* [[Настройка OpenVPN на FreeBSD]]&lt;br /&gt;
* [[Настройка внешней авторизации в Apache]]&lt;br /&gt;
* [[Настройка мониторинга MySQL с помощью Zabbix]]&lt;br /&gt;
* [[Настройка ограничений на отправку в Exim (ratelimit)]]&lt;br /&gt;
* [[Обновление часового пояса на FreeBSD]]&lt;br /&gt;
* [[Ограничение доступа к Squid (по адресу, по паролю, по времени)]]&lt;br /&gt;
* [[Отказ от SSLv2 и SSLv3 с уведомлением пользователей]]&lt;br /&gt;
* [[Отключение правил форума при регистрации]]&lt;br /&gt;
* [[Отключение формы обратной связи‎‎]]&lt;br /&gt;
* [[Отправка файла по почте через консоль]]&lt;br /&gt;
* [[Поиск во FreeBSD]]&lt;br /&gt;
* [[Поиск установленных соединений]]&lt;br /&gt;
* [[Почтовый сервер на FreeBSD 15.1: Exim и Dovecot]]&lt;br /&gt;
* [[Правильный перезапуск сети и маршрутизации во FreeBSD]]&lt;br /&gt;
* [[Проверка часового пояса]]&lt;br /&gt;
* [[Репликация MySQL]]&lt;br /&gt;
* [[Создание раздела более 2 ТБ]]&lt;br /&gt;
* [[Создание раздела с помощью parted]]&lt;br /&gt;
* [[Тестирование скорости чтения и записи жёсткого диска]]&lt;br /&gt;
* [[Удаление ^M]]&lt;br /&gt;
* [[Удаление Powered by vBulletin из заголовка страницы]]&lt;br /&gt;
* [[Управление пользователями с помощью bash-скриптов]]&lt;br /&gt;
* [[Управление разделами с помощью gpart]]&lt;br /&gt;
* [[Установка Bareos на FreeBSD]]&lt;br /&gt;
* [[Установка и настройка git на FreeBSD (freebsd+nginx+fcgiwrap+git+gitweb)]]&lt;br /&gt;
* [[Установка zabbix-agent на SUSE Linux Enterprise Server]]&lt;br /&gt;
* [[error_reporting и вывод на экран]]&lt;br /&gt;
* [[.htaccess и mod rewrite в nginx или как я мигрировал с Apache]]&lt;br /&gt;
* [[Модули для Apache - мой мини-комплект]]&lt;br /&gt;
* [[Apache: Couldn&#039;t create accept lock]]&lt;br /&gt;
* [[Out of inodes]]&lt;br /&gt;
* [[RHEL обновление лицензии]]&lt;br /&gt;
* [[Toshiba, обновление BIOS, проблемы с яркостью экрана, регулятором громкости]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=669</id>
		<title>Почтовый сервер на FreeBSD 15.1: Exim и Dovecot</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9F%D0%BE%D1%87%D1%82%D0%BE%D0%B2%D1%8B%D0%B9_%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80_%D0%BD%D0%B0_FreeBSD_15.1:_Exim_%D0%B8_Dovecot&amp;diff=669"/>
		<updated>2026-10-29T02:25:35Z</updated>

		<summary type="html">&lt;p&gt;Bas: Аудит публикации: заменить LAN-адреса документационными TEST-NET, уточнить примеры; секретов не обнаружено&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Почтовый сервер на FreeBSD 15.1: Exim и Dovecot&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Самостоятельная инструкция для одного сервера: Exim принимает SMTP и submission, Dovecot предоставляет IMAPS, а доставка в Maildir выполняется через локальный LMTP-сокет. Пользователи и алиасы хранятся в файлах, SQL не требуется. Почта, очередь и дополнительные данные размещаются на отдельной UFS в /opt. Сертификат выпускается после подготовки конфигураций: до этого сетевые почтовые службы не запускаются.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Все имена и адреса ниже — примеры.&#039;&#039;&#039; Заменить example.org и example.net своими доменами, mail.example.org — своим FQDN, 192.0.2.10 — реальным публичным IPv4, 198.51.100.20 — адресом сервера в LAN. 192.0.2.0/24 и 198.51.100.0/24 зарезервированы для документации (RFC 5737) и не являются рабочими адресами инфраструктуры. В примерах 198.51.100.20 — условный LAN-адрес сервера, 198.51.100.1 — условный шлюз; заменить их своими значениями. Домены example.org и example.net также предназначены для примеров. Публичные адреса документации, поставщиков и источников баз приведены намеренно; учётные данные для доступа к ним не публикуются. Команды для чистой системы выполняются от root в /bin/sh; существующие конфигурации сначала сравнить, не заменять вслепую. Проверенная основа: FreeBSD 15.1, Exim 4.100, Dovecot 2.4.5. Для других веток Dovecot синтаксис отличается.&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 1. Схема и требования ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Интернет&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;25&amp;amp;#32;/&amp;amp;#32;STARTTLS&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;465&amp;amp;#32;TLS&amp;amp;#32;или&amp;amp;#32;587&amp;amp;#32;STARTTLS&amp;amp;#32;+&amp;amp;#32;AUTH&amp;amp;#32;→&amp;amp;#32;Exim&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;LMTP&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;/opt/mail/vhosts/domain/user/Maildir&lt;br /&gt;
Exim&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;AUTH&amp;amp;#32;Unix&amp;amp;#32;socket&amp;amp;#32;→&amp;amp;#32;Dovecot&amp;amp;#32;→&amp;amp;#32;файловый&amp;amp;#32;каталог&amp;amp;#32;пользователей&lt;br /&gt;
Клиент&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;→&amp;amp;#32;TCP&amp;amp;#32;993&amp;amp;#32;TLS&amp;amp;#32;→&amp;amp;#32;Dovecot&lt;br /&gt;
SnappyMail&amp;amp;#32;(необязательно)&amp;amp;#32;→&amp;amp;#32;те&amp;amp;#32;же&amp;amp;#32;IMAPS&amp;amp;#32;и&amp;amp;#32;SMTP&amp;amp;#32;AUTH&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Выделенный пустой диск da1; системный диск — другой диск. Тип ФС — UFS.&lt;br /&gt;
* Статический адрес сервера и действующий маршрут по умолчанию. При NAT пробросить TCP 25, 465, 587, 993 на сервер. Для SnappyMail понадобится HTTPS 443 или отдельный обратный прокси.&lt;br /&gt;
* Провайдер должен разрешать исходящий и входящий TCP 25. PTR публичного IP заказывается у владельца адреса: mail.example.org. Прямая A-запись должна возвращать тот же IP.&lt;br /&gt;
* Основной вариант IPv4. AAAA не публиковать, пока IPv6 не настроен полностью; в приведенном Exim IPv6 выключен.&lt;br /&gt;
* Не публиковать MX нового сервера до запуска и проверки. Смена MX переключает новые письма, но не переносит старую почту.&lt;br /&gt;
* Антиспам и фаервол не входят в базовую последовательность. Rspamd — отдельный этап, ClamAV описан ниже.&lt;br /&gt;
&lt;br /&gt;
== 2. Проверить систему без изменений ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
id&lt;br /&gt;
freebsd-version&amp;amp;#32;-kru&lt;br /&gt;
uname&amp;amp;#32;-m&lt;br /&gt;
hostname&amp;amp;#32;-f&lt;br /&gt;
ifconfig&amp;amp;#32;-a&lt;br /&gt;
netstat&amp;amp;#32;-rn&lt;br /&gt;
cat&amp;amp;#32;/etc/resolv.conf&lt;br /&gt;
mount&amp;amp;#32;-p&lt;br /&gt;
df&amp;amp;#32;-hT&lt;br /&gt;
swapinfo&amp;amp;#32;-h&lt;br /&gt;
geom&amp;amp;#32;disk&amp;amp;#32;list&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-p&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-p&amp;amp;#32;da1&lt;br /&gt;
glabel&amp;amp;#32;status&lt;br /&gt;
fstyp&amp;amp;#32;/dev/da1&lt;br /&gt;
file&amp;amp;#32;-s&amp;amp;#32;/dev/da1&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
ls&amp;amp;#32;-ld&amp;amp;#32;/opt&lt;br /&gt;
cat&amp;amp;#32;/etc/fstab&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
При необходимости настроить имя и шлюз, подставив реальные параметры сети. Если интерфейс уже настроен, не применять ifconfig повторно через удалённый сеанс без необходимости.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sysrc&amp;amp;#32;hostname=mail.example.org&lt;br /&gt;
hostname&amp;amp;#32;mail.example.org&lt;br /&gt;
sysrc&amp;amp;#32;defaultrouter=198.51.100.1&lt;br /&gt;
#&amp;amp;#32;Только&amp;amp;#32;если&amp;amp;#32;в&amp;amp;#32;работающей&amp;amp;#32;таблице&amp;amp;#32;отсутствует&amp;amp;#32;default&amp;amp;#32;route:&lt;br /&gt;
route&amp;amp;#32;add&amp;amp;#32;default&amp;amp;#32;198.51.100.1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Однократно создать UFS на пустом da1 ==&lt;br /&gt;
&#039;&#039;&#039;ВНИМАНИЕ: gpart create и newfs — разрушающие команды. На диске с существующими данными их повторять нельзя.&#039;&#039;&#039; Проверить модель, размер, серийный номер, разделы, метки, mount, swap и GEOM-зависимости. Отсутствие распознанной ФС не доказывает, что диск пуст. /opt не должен содержать файлов, которые скроются после монтирования.&lt;br /&gt;
&lt;br /&gt;
Для нового диска, который должен быть полностью нулевым, дополнительная проверка чтением всех байтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
disk_bytes=$(diskinfo&amp;amp;#32;/dev/da1&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;{print&amp;amp;#32;$3}&#039;)&lt;br /&gt;
test&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;-gt&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;-n&amp;amp;#32;&amp;quot;$disk_bytes&amp;quot;&amp;amp;#32;/dev/da1&amp;amp;#32;/dev/zero&lt;br /&gt;
printf&amp;amp;#32;&#039;cmp&amp;amp;#32;status:&amp;amp;#32;%s\n&#039;&amp;amp;#32;&amp;quot;$?&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
0 означает нулевое содержимое, 1 — ненулевые данные, больше 1 — ошибку проверки. В двух последних случаях остановиться и разобраться. На уже созданной UFS результат 1 нормален и не является поводом форматировать её.&lt;br /&gt;
&lt;br /&gt;
Только после проверки действительно пустого выделенного диска:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gpart&amp;amp;#32;create&amp;amp;#32;-s&amp;amp;#32;gpt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;add&amp;amp;#32;-a&amp;amp;#32;1m&amp;amp;#32;-t&amp;amp;#32;freebsd-ufs&amp;amp;#32;-l&amp;amp;#32;mailopt&amp;amp;#32;da1&lt;br /&gt;
gpart&amp;amp;#32;show&amp;amp;#32;-pl&amp;amp;#32;da1&lt;br /&gt;
newfs&amp;amp;#32;-U&amp;amp;#32;-L&amp;amp;#32;mailopt&amp;amp;#32;/dev/gpt/mailopt&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt&lt;br /&gt;
mount&amp;amp;#32;-o&amp;amp;#32;rw,noatime&amp;amp;#32;/dev/gpt/mailopt&amp;amp;#32;/opt&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/opt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/fstab добавить ровно одну запись, предварительно исключив дубликаты:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/gpt/mailopt&amp;amp;#32;/opt&amp;amp;#32;ufs&amp;amp;#32;rw,noatime&amp;amp;#32;2&amp;amp;#32;2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При повторной установке существующую UFS проверяют и монтируют, не создают заново.&lt;br /&gt;
&lt;br /&gt;
== 4. Пакеты, пользователи и каталоги ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;update&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
#&amp;amp;#32;После&amp;amp;#32;проверки&amp;amp;#32;плана:&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;exim&amp;amp;#32;dovecot24&amp;amp;#32;swaks&amp;amp;#32;ca_root_nss&amp;amp;#32;acme.sh&lt;br /&gt;
sysrc&amp;amp;#32;exim_enable=NO&amp;amp;#32;dovecot_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_enable=NONE&amp;amp;#32;sendmail_submit_enable=NO&lt;br /&gt;
sysrc&amp;amp;#32;sendmail_outbound_enable=NO&amp;amp;#32;sendmail_msp_queue_enable=NO&lt;br /&gt;
service&amp;amp;#32;sendmail&amp;amp;#32;onestop&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/dovecot&amp;amp;#32;--version&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;mailnull&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;mail&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ошибку «служба sendmail не запущена» можно принять только после проверки, что другой SMTP-процесс не занимает порт 25. Существующий рабочий почтовый сервер нельзя останавливать этой последовательностью без миграции.&lt;br /&gt;
&lt;br /&gt;
В пакете Exim должны присутствовать OpenSSL, Dovecot authenticator, LMTP и Content_Scanning для необязательного ClamAV. Проверить, что UID и GID 5000 свободны. Если заняты, выбрать другие и согласованно заменить во всех дальнейших настройках.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;-u&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
#&amp;amp;#32;Следующие&amp;amp;#32;две&amp;amp;#32;команды&amp;amp;#32;только&amp;amp;#32;если&amp;amp;#32;идентификаторы&amp;amp;#32;свободны:&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;5000&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;vmail&amp;amp;#32;-u&amp;amp;#32;5000&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-d&amp;amp;#32;/opt/mail&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/vhosts&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;mailnull&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/exim-spool&amp;amp;#32;/var/log/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;dovecot&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/virtual-mail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;mail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/usr/local/etc/exim/dkim&amp;amp;#32;/usr/local/etc/mail-certs&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/etc/rc.conf.d&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать файлы, если они отсутствуют; не обнулять уже существующий каталог:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
touch&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chown&amp;amp;#32;root:dovecot&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;domains&amp;amp;#32;aliases&amp;amp;#32;mailboxes&amp;amp;#32;sender_owners;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;touch&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/exim/$name&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/exim сохранить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/exim-spool&amp;amp;#32;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/dovecot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/mail/vhosts&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 5. Полная конфигурация Exim ==&lt;br /&gt;
Сохранить следующий текст как /usr/local/etc/exim/configure, владелец root:mail, режим 0640. Это конфигурация без ClamAV; антивирус добавляется отдельным этапом. На 465/587 обязательны TLS и AUTH; на 25 AUTH не предлагается. Межсерверный SMTP 25 использует STARTTLS по возможности. Relay без аутентификации запрещён, включая localhost и LAN. Получатели обслуживаемых доменов проверяются до приема письма; авторизованный пользователь может отправлять только со своих адресов и алиасов.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;/&amp;amp;#32;Exim&amp;amp;#32;/&amp;amp;#32;virtual&amp;amp;#32;users&amp;amp;#32;/&amp;amp;#32;Dovecot&amp;amp;#32;LMTP&amp;amp;#32;and&amp;amp;#32;AUTH.&lt;br /&gt;
#&amp;amp;#32;Render&amp;amp;#32;placeholders,&amp;amp;#32;validate&amp;amp;#32;with&amp;amp;#32;exim&amp;amp;#32;-C&amp;amp;#32;...&amp;amp;#32;-bV,&amp;amp;#32;then&amp;amp;#32;install&amp;amp;#32;as&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/exim/configure.&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;before&amp;amp;#32;certificates&amp;amp;#32;exist.&lt;br /&gt;
#&amp;amp;#32;Runtime&amp;amp;#32;identity&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;official&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;is&amp;amp;#32;mailnull:mail.&lt;br /&gt;
&lt;br /&gt;
primary_hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
qualify_domain&amp;amp;#32;=&amp;amp;#32;example.org&lt;br /&gt;
keep_environment&amp;amp;#32;=&lt;br /&gt;
local_interfaces&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;:&amp;amp;#32;198.51.100.20&lt;br /&gt;
disable_ipv6&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
domainlist&amp;amp;#32;local_domains&amp;amp;#32;=&amp;amp;#32;@&amp;amp;#32;:&amp;amp;#32;/usr/local/etc/exim/domains&lt;br /&gt;
&lt;br /&gt;
spool_directory&amp;amp;#32;=&amp;amp;#32;/opt/mail/exim-spool&lt;br /&gt;
log_file_path&amp;amp;#32;=&amp;amp;#32;/var/log/exim/%slog&lt;br /&gt;
never_users&amp;amp;#32;=&amp;amp;#32;root&lt;br /&gt;
message_size_limit&amp;amp;#32;=&amp;amp;#32;50M&lt;br /&gt;
&lt;br /&gt;
daemon_smtp_ports&amp;amp;#32;=&amp;amp;#32;25&amp;amp;#32;:&amp;amp;#32;465&amp;amp;#32;:&amp;amp;#32;587&lt;br /&gt;
tls_on_connect_ports&amp;amp;#32;=&amp;amp;#32;465&lt;br /&gt;
&lt;br /&gt;
tls_advertise_hosts&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
tls_certificate&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
tls_privatekey&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;option&amp;amp;#32;requires&amp;amp;#32;an&amp;amp;#32;OpenSSL&amp;amp;#32;build&amp;amp;#32;(the&amp;amp;#32;default&amp;amp;#32;FreeBSD&amp;amp;#32;port).&lt;br /&gt;
#&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;standard&amp;amp;#32;safety&amp;amp;#32;flags&amp;amp;#32;while&amp;amp;#32;disabling&amp;amp;#32;TLS&amp;amp;#32;1.0&amp;amp;#32;and&amp;amp;#32;1.1.&lt;br /&gt;
openssl_options&amp;amp;#32;=&amp;amp;#32;+no_sslv2&amp;amp;#32;+no_sslv3&amp;amp;#32;+single_dh_use&amp;amp;#32;+no_ticket&amp;amp;#32;+no_renegotiation&amp;amp;#32;+no_tlsv1&amp;amp;#32;+no_tlsv1_1&lt;br /&gt;
tls_require_ciphers&amp;amp;#32;=&amp;amp;#32;HIGH:!aNULL:!eNULL:!MD5:!RC4:!3DES&lt;br /&gt;
&lt;br /&gt;
acl_smtp_auth&amp;amp;#32;=&amp;amp;#32;acl_check_auth&lt;br /&gt;
acl_smtp_mail&amp;amp;#32;=&amp;amp;#32;acl_check_mail&lt;br /&gt;
acl_smtp_rcpt&amp;amp;#32;=&amp;amp;#32;acl_check_rcpt&lt;br /&gt;
acl_smtp_data&amp;amp;#32;=&amp;amp;#32;acl_check_data&lt;br /&gt;
acl_smtp_vrfy&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
acl_smtp_expn&amp;amp;#32;=&amp;amp;#32;deny&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;acl&lt;br /&gt;
&lt;br /&gt;
acl_check_auth:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;before&amp;amp;#32;authentication&lt;br /&gt;
&lt;br /&gt;
acl_check_mail:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!encrypted&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;TLS&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;inlist{$received_port}{465:587}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Authentication&amp;amp;#32;is&amp;amp;#32;required&amp;amp;#32;on&amp;amp;#32;submission&amp;amp;#32;ports&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$sender_address}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Envelope&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
acl_check_rcpt:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Validate&amp;amp;#32;hosted&amp;amp;#32;recipients&amp;amp;#32;even&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;sender&amp;amp;#32;is&amp;amp;#32;authenticated.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;There&amp;amp;#32;is&amp;amp;#32;deliberately&amp;amp;#32;no&amp;amp;#32;localhost/LAN&amp;amp;#32;relay&amp;amp;#32;exemption.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Relay&amp;amp;#32;not&amp;amp;#32;permitted&lt;br /&gt;
&lt;br /&gt;
acl_check_data:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Only&amp;amp;#32;submitted&amp;amp;#32;authenticated&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;subjected&amp;amp;#32;to&amp;amp;#32;sender&amp;amp;#32;ownership.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Raw&amp;amp;#32;From&amp;amp;#32;preserves&amp;amp;#32;encoded&amp;amp;#32;display&amp;amp;#32;names&amp;amp;#32;containing&amp;amp;#32;commas.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!verify&amp;amp;#32;=&amp;amp;#32;header_syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Invalid&amp;amp;#32;message&amp;amp;#32;header&amp;amp;#32;syntax&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;acl_m_from_addresses&amp;amp;#32;=&amp;amp;#32;${addresses:&amp;amp;gt;:$rh_from:}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!eq{${listcount:$acl_m_from_addresses}}{1}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;A&amp;amp;#32;single&amp;amp;#32;valid&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;required&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;!inlisti{${lc:$authenticated_id}}{${lookup{${lc:$acl_m_from_addresses}}lsearch{/usr/local/etc/exim/sender_owners}{$value}{}}}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Header&amp;amp;#32;From&amp;amp;#32;address&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;authorized&amp;amp;#32;for&amp;amp;#32;this&amp;amp;#32;account&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;accept&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;routers&lt;br /&gt;
&lt;br /&gt;
service_hostname_postmaster:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;@&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;local_parts&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;:&amp;amp;#32;postmaster&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;alice@example.org&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_aliases:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;redirect&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;data&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/aliases}{$value}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_file&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;forbid_pipe&lt;br /&gt;
&lt;br /&gt;
virtual_mailboxes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;accept&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;condition&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$local_part@$domain}}lsearch{/usr/local/etc/exim/mailboxes}{yes}{no}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;dovecot_lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cannot_route_message&amp;amp;#32;=&amp;amp;#32;Unknown&amp;amp;#32;local&amp;amp;#32;recipient&lt;br /&gt;
&lt;br /&gt;
remote_dns:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dnslookup&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;domains&amp;amp;#32;=&amp;amp;#32;!&amp;amp;#32;+local_domains&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;transport&amp;amp;#32;=&amp;amp;#32;remote_smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;ignore_target_hosts&amp;amp;#32;=&amp;amp;#32;0.0.0.0&amp;amp;#32;:&amp;amp;#32;127.0.0.0/8&amp;amp;#32;:&amp;amp;#32;::1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;no_more&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;transports&lt;br /&gt;
&lt;br /&gt;
remote_smtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;smtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_domain&amp;amp;#32;=&amp;amp;#32;${lookup{${lc:$sender_address_domain}}lsearch,ret=key{/usr/local/etc/exim/domains}{$value}{}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_selector&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_private_key&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;exists{/usr/local/etc/exim/dkim/$dkim_domain.key}{/usr/local/etc/exim/dkim/$dkim_domain.key}{0}}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_hash&amp;amp;#32;=&amp;amp;#32;sha256&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_canon&amp;amp;#32;=&amp;amp;#32;relaxed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;dkim_strict&amp;amp;#32;=&amp;amp;#32;true&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Opportunistic&amp;amp;#32;STARTTLS&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;normal&amp;amp;#32;Exim&amp;amp;#32;default&amp;amp;#32;for&amp;amp;#32;remote&amp;amp;#32;delivery.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;tls_verify_certificates&amp;amp;#32;=&amp;amp;#32;system&lt;br /&gt;
&lt;br /&gt;
dovecot_lmtp:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;lmtp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/lmtp-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;batch_max&amp;amp;#32;=&amp;amp;#32;20&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;LMTP&amp;amp;#32;RCPT&amp;amp;#32;contains&amp;amp;#32;the&amp;amp;#32;full&amp;amp;#32;canonical&amp;amp;#32;local_part@domain.&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;retry&lt;br /&gt;
&lt;br /&gt;
*&amp;amp;#32;*&amp;amp;#32;F,2h,15m;&amp;amp;#32;G,16h,1h,1.5;&amp;amp;#32;F,4d,6h&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;rewrite&lt;br /&gt;
&lt;br /&gt;
begin&amp;amp;#32;authenticators&lt;br /&gt;
&lt;br /&gt;
dovecot_plain:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;PLAIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&lt;br /&gt;
dovecot_login:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;driver&amp;amp;#32;=&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;public_name&amp;amp;#32;=&amp;amp;#32;LOGIN&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_socket&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot/auth-exim&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_set_id&amp;amp;#32;=&amp;amp;#32;$auth1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;server_advertise_condition&amp;amp;#32;=&amp;amp;#32;${if&amp;amp;#32;and{{def:tls_in_cipher}{inlist{$received_port}{465:587}}}{yes}{no}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 6. Полная конфигурация Dovecot 2.4 ==&lt;br /&gt;
Сохранить как /usr/local/etc/dovecot/dovecot.conf, root:wheel, 0644. Не подключать одновременно пакетное дерево conf.d с повторяющимися настройками. Важен type = auth-legacy у сокета Exim в Dovecot 2.4. TCP 143 и стандартный открытый LMTP-сокет отключены.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dovecot_config_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
dovecot_storage_version&amp;amp;#32;=&amp;amp;#32;2.4.4&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;15.1,&amp;amp;#32;installed&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5_2,&amp;amp;#32;Exim&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;host.&lt;br /&gt;
#&amp;amp;#32;vmail&amp;amp;#32;has&amp;amp;#32;UID/GID&amp;amp;#32;5000.&amp;amp;#32;Mail&amp;amp;#32;hostname&amp;amp;#32;is&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf,&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;dovecot_enable=NO&amp;amp;#32;until&amp;amp;#32;certificates&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;checks&amp;amp;#32;pass.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;include&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;package&amp;amp;#39;s&amp;amp;#32;example&amp;amp;#32;conf.d&amp;amp;#32;tree:&amp;amp;#32;this&amp;amp;#32;is&amp;amp;#32;a&amp;amp;#32;complete&amp;amp;#32;config.&lt;br /&gt;
&lt;br /&gt;
base_dir&amp;amp;#32;=&amp;amp;#32;/var/run/dovecot&lt;br /&gt;
state_dir&amp;amp;#32;=&amp;amp;#32;/var/db/dovecot&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;0.0.0.0&lt;br /&gt;
protocols&amp;amp;#32;=&amp;amp;#32;imap&amp;amp;#32;lmtp&lt;br /&gt;
hostname&amp;amp;#32;=&amp;amp;#32;mail.example.org&lt;br /&gt;
postmaster_address&amp;amp;#32;=&amp;amp;#32;postmaster@example.org&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;canonical&amp;amp;#32;login&amp;amp;#32;per&amp;amp;#32;mailbox;&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;and&amp;amp;#32;normalize&amp;amp;#32;case.&lt;br /&gt;
auth_mechanisms&amp;amp;#32;=&amp;amp;#32;plain&amp;amp;#32;login&lt;br /&gt;
auth_allow_cleartext&amp;amp;#32;=&amp;amp;#32;no&lt;br /&gt;
auth_username_format&amp;amp;#32;=&amp;amp;#32;%{user&amp;amp;#32;|&amp;amp;#32;lower}&lt;br /&gt;
auth_verbose&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;One&amp;amp;#32;service&amp;amp;#32;UID/GID&amp;amp;#32;for&amp;amp;#32;all&amp;amp;#32;virtual&amp;amp;#32;mailboxes.&lt;br /&gt;
first_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_uid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
first_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
last_valid_gid&amp;amp;#32;=&amp;amp;#32;5000&lt;br /&gt;
&lt;br /&gt;
mail_driver&amp;amp;#32;=&amp;amp;#32;maildir&lt;br /&gt;
mail_path&amp;amp;#32;=&amp;amp;#32;~/Maildir&lt;br /&gt;
mail_uid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
mail_gid&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Same&amp;amp;#32;file&amp;amp;#32;provides&amp;amp;#32;passdb&amp;amp;#32;and&amp;amp;#32;an&amp;amp;#32;existence-checking&amp;amp;#32;userdb.&amp;amp;#32;In&amp;amp;#32;particular,&lt;br /&gt;
#&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;replace&amp;amp;#32;userdb&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;static&amp;amp;#32;backend&amp;amp;#32;that&amp;amp;#32;accepts&amp;amp;#32;arbitrary&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;File&amp;amp;#32;mode/owner:&amp;amp;#32;root:dovecot&amp;amp;#32;0640.&amp;amp;#32;All&amp;amp;#32;entries&amp;amp;#32;use&amp;amp;#32;full&amp;amp;#32;user@domain.&lt;br /&gt;
passdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
userdb&amp;amp;#32;passwd-file&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;passwd_file_path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/virtual-mail/users&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;IMAP&amp;amp;#32;access&amp;amp;#32;is&amp;amp;#32;TLS-only&amp;amp;#32;on&amp;amp;#32;TCP&amp;amp;#32;993.&amp;amp;#32;Never&amp;amp;#32;run&amp;amp;#32;the&amp;amp;#32;service&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;last&lt;br /&gt;
#&amp;amp;#32;certificate-creation/installation&amp;amp;#32;step.&amp;amp;#32;These&amp;amp;#32;settings&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;issue&amp;amp;#32;a&amp;amp;#32;cert.&lt;br /&gt;
ssl&amp;amp;#32;=&amp;amp;#32;required&lt;br /&gt;
ssl_min_protocol&amp;amp;#32;=&amp;amp;#32;TLSv1.2&lt;br /&gt;
ssl_server_cert_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&lt;br /&gt;
ssl_server_key_file&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&lt;br /&gt;
&lt;br /&gt;
service&amp;amp;#32;imap-login&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inet_listener&amp;amp;#32;imaps&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;port&amp;amp;#32;=&amp;amp;#32;993&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#39;s&amp;amp;#32;Dovecot&amp;amp;#32;authenticator&amp;amp;#32;currently&amp;amp;#32;needs&amp;amp;#32;auth-legacy&amp;amp;#32;with&amp;amp;#32;Dovecot&amp;amp;#32;2.4.&lt;br /&gt;
#&amp;amp;#32;root&amp;amp;#32;creates&amp;amp;#32;the&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;Exim;&amp;amp;#32;mailnull&amp;amp;#32;is&amp;amp;#32;the&amp;amp;#32;FreeBSD&amp;amp;#32;Exim&amp;amp;#32;account.&lt;br /&gt;
service&amp;amp;#32;auth&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;auth-legacy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Local&amp;amp;#32;LMTP&amp;amp;#32;runs&amp;amp;#32;as&amp;amp;#32;vmail&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;this&amp;amp;#32;userdb&amp;amp;#32;socket&amp;amp;#32;for&amp;amp;#32;lookups.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;auth-userdb&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;the&amp;amp;#32;explicitly&amp;amp;#32;named&amp;amp;#32;Exim&amp;amp;#32;socket&amp;amp;#32;may&amp;amp;#32;receive&amp;amp;#32;mail.&amp;amp;#32;Disable&amp;amp;#32;the&lt;br /&gt;
#&amp;amp;#32;package/default&amp;amp;#32;LMTP&amp;amp;#32;listener,&amp;amp;#32;whose&amp;amp;#32;default&amp;amp;#32;mode&amp;amp;#32;is&amp;amp;#32;otherwise&amp;amp;#32;0666.&lt;br /&gt;
service&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;vmail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;unix_listener&amp;amp;#32;lmtp-exim&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;user&amp;amp;#32;=&amp;amp;#32;mailnull&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;group&amp;amp;#32;=&amp;amp;#32;mail&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode&amp;amp;#32;=&amp;amp;#32;0600&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Standard&amp;amp;#32;folders&amp;amp;#32;let&amp;amp;#32;desktop&amp;amp;#32;clients&amp;amp;#32;and&amp;amp;#32;future&amp;amp;#32;SnappyMail&amp;amp;#32;agree&amp;amp;#32;on&amp;amp;#32;names.&lt;br /&gt;
#&amp;amp;#32;Junk&amp;amp;#32;may&amp;amp;#32;be&amp;amp;#32;added&amp;amp;#32;later&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;optional&amp;amp;#32;spam&amp;amp;#32;workflow.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;inbox&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;separator&amp;amp;#32;=&amp;amp;#32;/&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Drafts&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Drafts&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Sent&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Sent&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Trash&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Trash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Archive&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Archive&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Official&amp;amp;#32;references&amp;amp;#32;checked&amp;amp;#32;while&amp;amp;#32;preparing&amp;amp;#32;this&amp;amp;#32;template:&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/howto/sasl/exim.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/2.4.4/core/config/namespaces.html&lt;br /&gt;
#&amp;amp;#32;https://doc.dovecot.org/latest/core/config/ssl.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 7. Управление доменами, ящиками, паролями и алиасами ==&lt;br /&gt;
Одна каноническая запись users имеет формат:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alice@example.org:{BLF-CRYPT}REPLACE_WITH_REAL_HASH:5000:5000::/opt/mail/vhosts/example.org/alice::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это лишь пояснение формата, не рабочий пароль. Пароли вводятся интерактивно через doveadm; передавать их аргументом командной строки не следует. Полный адрес user@domain — логин и идентификатор ящика; alice@example.org и alice@example.net — разные пользователи.&lt;br /&gt;
&lt;br /&gt;
Следующий готовый скрипт сохранить как /usr/local/sbin/mail-user (root:wheel, 0750). Он использует lockf, проверяет каталог, заменяет отдельные файлы атомарным rename и согласованно пересоздаёт recipient/sender maps. Алиас направляется только на существующего канонического пользователя того же домена и одновременно разрешает отправку с этого адреса. Удаление ящиков намеренно не автоматизировано.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;mail-user:&amp;amp;#32;conservative&amp;amp;#32;file-catalog&amp;amp;#32;administration&amp;amp;#32;for&amp;amp;#32;Exim&amp;amp;#32;+&amp;amp;#32;Dovecot.&lt;br /&gt;
#&amp;amp;#32;Passwords&amp;amp;#32;are&amp;amp;#32;entered&amp;amp;#32;only&amp;amp;#32;into&amp;amp;#32;doveadm&amp;amp;#39;s&amp;amp;#32;terminal&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;delete&amp;amp;#32;operation,&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;backups,&amp;amp;#32;no&amp;amp;#32;service&amp;amp;#32;startup.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
STATE=/usr/local/etc/virtual-mail&lt;br /&gt;
USERS=$STATE/users&lt;br /&gt;
EXIMDIR=/usr/local/etc/exim&lt;br /&gt;
DOMAINS=$EXIMDIR/domains&lt;br /&gt;
ALIASES=$EXIMDIR/aliases&lt;br /&gt;
MAILBOXES=$EXIMDIR/mailboxes&lt;br /&gt;
OWNERS=$EXIMDIR/sender_owners&lt;br /&gt;
MAILROOT=/opt/mail/vhosts&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$EXIMDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;configuration&amp;amp;#32;directories&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.lock&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Usage:&amp;amp;#32;mail-user&amp;amp;#32;add-domain&amp;amp;#32;DOMAIN&amp;amp;#32;|&amp;amp;#32;add-user&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;set-password&amp;amp;#32;ADDRESS&amp;amp;#32;|&amp;amp;#32;add-alias&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL&amp;amp;quot;&lt;br /&gt;
action=$1&lt;br /&gt;
shift&lt;br /&gt;
PASSWORD_HASH_FILE=&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-domain&amp;amp;#32;takes&amp;amp;#32;one&amp;amp;#32;domain.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user|set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;3&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;--hash-file&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;PASSWORD_HASH_FILE=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;1&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;ADDRESS,&amp;amp;#32;optionally&amp;amp;#32;followed&amp;amp;#32;by&amp;amp;#32;--hash-file&amp;amp;#32;ROOT_ONLY_FILE.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;2&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;add-alias&amp;amp;#32;takes&amp;amp;#32;ALIAS&amp;amp;#32;CANONICAL.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Unknown&amp;amp;#32;operation.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;UFS&amp;amp;#32;before&amp;amp;#32;mail&amp;amp;#32;administration.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
EXIMGROUP=$(&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bP&amp;amp;#32;exim_group&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;=&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;Exim&amp;amp;#32;runtime&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;dovecot&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;dovecot&amp;amp;#32;group&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;exist.&amp;amp;quot;&lt;br /&gt;
VUID=$(id&amp;amp;#32;-u&amp;amp;#32;vmail)&lt;br /&gt;
VGID=$(id&amp;amp;#32;-g&amp;amp;#32;vmail)&lt;br /&gt;
&lt;br /&gt;
for&amp;amp;#32;file&amp;amp;#32;in&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Refusing&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;catalog.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;:&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;)&amp;amp;#32;chown&amp;amp;#32;root:dovecot&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$file&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;mail&amp;amp;#32;catalog&amp;amp;#32;contains&amp;amp;#32;a&amp;amp;#32;non-regular&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$STATE/.mail-user.XXXXXX&amp;amp;quot;)&lt;br /&gt;
pending=&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-z&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
normalize()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;[:upper:]&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;[:lower:]&amp;amp;#39;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
validate_domain()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vd}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;253&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;|.*|*.|*..*|*[!a-z0-9.-]*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain:&amp;amp;#32;$vd&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;oldifs=$IFS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;--&amp;amp;#32;$vd&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;IFS=$oldifs&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;label&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#label}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;63&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$label&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;-*|*-)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;domain&amp;amp;#32;label.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_address()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;va=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$va&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*@*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Use&amp;amp;#32;a&amp;amp;#32;full&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vl=${va%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vd=${va#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*@*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;email&amp;amp;#32;address.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-ge&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${#vl}&amp;amp;quot;&amp;amp;#32;-le&amp;amp;#32;64&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part&amp;amp;#32;length.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[a-z0-9]*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Local&amp;amp;#32;part&amp;amp;#32;must&amp;amp;#32;start&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;letter&amp;amp;#32;or&amp;amp;#32;digit.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$vl&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*[!a-z0-9._+-]*|*.|*..*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;local&amp;amp;#32;part.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$vd&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
has_domain()&amp;amp;#32;{&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;}&lt;br /&gt;
has_user()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
has_alias()&amp;amp;#32;{&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$1&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;$0&amp;amp;#32;!~&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1);&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;key)&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
replace_file()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;source=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;destination=$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;group=$3&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=$(mktemp&amp;amp;#32;&amp;amp;quot;$(dirname&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;)/.mail-user-file.XXXXXX&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$source&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:&amp;amp;quot;$group&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mv&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$pending&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$destination&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;pending=&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Validate&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;catalog&amp;amp;#32;before&amp;amp;#32;deriving&amp;amp;#32;Exim&amp;amp;#32;recipient&amp;amp;#32;maps.&lt;br /&gt;
validate_catalog()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;expected_uid=&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;expected_gid=&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;-v&amp;amp;#32;mailroot=&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;8&amp;amp;#32;||&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;!~&amp;amp;#32;/^\{[A-Z0-9-]+\}/&amp;amp;#32;||&amp;amp;#32;seen[$1]++&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;split($1,&amp;amp;#32;address,&amp;amp;#32;&amp;amp;quot;@&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($3&amp;amp;#32;!=&amp;amp;#32;expected_uid&amp;amp;#32;||&amp;amp;#32;$4&amp;amp;#32;!=&amp;amp;#32;expected_gid&amp;amp;#32;||&amp;amp;#32;$6&amp;amp;#32;!=&amp;amp;#32;mailroot&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[2]&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;address[1])&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;key;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$key&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Canonical&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;${key#*@}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#39;s&amp;amp;#32;domain&amp;amp;#32;is&amp;amp;#32;missing&amp;amp;#32;from&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/^[[:space:]]*(#|$)/&amp;amp;#32;{&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NF&amp;amp;#32;!=&amp;amp;#32;2&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gsub(/^[[:space:]]+|[[:space:]]+$/,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;,&amp;amp;#32;$2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;($1&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;seen[$1]++)&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;quot;&amp;amp;#32;$2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Malformed&amp;amp;#32;or&amp;amp;#32;duplicate&amp;amp;#32;alias&amp;amp;#32;record.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;IFS=:&amp;amp;#32;read&amp;amp;#32;-r&amp;amp;#32;alias&amp;amp;#32;target;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;addresses&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;lowercase.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;crosses&amp;amp;#32;domains.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;target&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;An&amp;amp;#32;alias&amp;amp;#32;conflicts&amp;amp;#32;with&amp;amp;#32;a&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;done&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
regenerate_maps()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;validate_catalog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;yes&amp;amp;quot;&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$0&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$0&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/user-keys&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;{&amp;amp;#32;print&amp;amp;#32;$1&amp;amp;#32;&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;$2&amp;amp;#32;}&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/alias-records&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/mailboxes&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILBOXES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/owners&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$OWNERS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
new_password_hash()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;/*)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;path&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;hash&amp;amp;#32;file.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;400|600)&amp;amp;#32;;;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Hash&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0400&amp;amp;#32;or&amp;amp;#32;0600.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(cat&amp;amp;#32;&amp;amp;quot;$PASSWORD_HASH_FILE&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;interactively&amp;amp;#32;in&amp;amp;#32;PuTTY&amp;amp;#32;to&amp;amp;#32;enter&amp;amp;#32;the&amp;amp;#32;mailbox&amp;amp;#32;password.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;DOVEADM=$(command&amp;amp;#32;-v&amp;amp;#32;doveadm)&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-l&amp;amp;#32;|&amp;amp;#32;tr&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;#39;\n&amp;amp;#39;&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;-Fx&amp;amp;#32;ARGON2ID&amp;amp;#32;&amp;amp;gt;/dev/null;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;ARGON2ID)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NEW_HASH=$(&amp;amp;quot;$DOVEADM&amp;amp;quot;&amp;amp;#32;-O&amp;amp;#32;pw&amp;amp;#32;-s&amp;amp;#32;BLF-CRYPT&amp;amp;#32;-r&amp;amp;#32;12)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\{ARGON2ID\}*|\{BLF-CRYPT\}*)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;doveadm&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;return&amp;amp;#32;a&amp;amp;#32;supported&amp;amp;#32;password&amp;amp;#32;hash.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;esac&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;case&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;*:*|*&amp;amp;#39;&lt;br /&gt;
&amp;amp;#39;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Invalid&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format.&amp;amp;quot;&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
validate_catalog&lt;br /&gt;
&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$action&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-domain)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Domain&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$DOMAINS&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Domain&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-user)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;domain=${address#*@}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;localpart=${address%%@*}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_domain&amp;amp;#32;&amp;amp;quot;$domain&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Add&amp;amp;#32;the&amp;amp;#32;domain&amp;amp;#32;first.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;home=$MAILROOT/$domain/$localpart&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$MAILROOT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$MAILROOT/$domain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/cur&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/new&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home/Maildir/tmp&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:%s:%s:%s::%s::\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VUID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$VGID&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$home&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Mailbox&amp;amp;#32;added:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;set-password)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;address=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;such&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;new_password_hash&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;awk&amp;amp;#32;receives&amp;amp;#32;the&amp;amp;#32;hash&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;root-only&amp;amp;#32;temporary&amp;amp;#32;input&amp;amp;#32;file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;never&amp;amp;#32;through&amp;amp;#32;a&amp;amp;#32;command-line&amp;amp;#32;argument&amp;amp;#32;or&amp;amp;#32;an&amp;amp;#32;environment&amp;amp;#32;variable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$NEW_HASH&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;unset&amp;amp;#32;NEW_HASH&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;-v&amp;amp;#32;OFS=:&amp;amp;#32;-v&amp;amp;#32;key=&amp;amp;quot;$address&amp;amp;quot;&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;NR&amp;amp;#32;==&amp;amp;#32;FNR&amp;amp;#32;{&amp;amp;#32;password&amp;amp;#32;=&amp;amp;#32;$0;&amp;amp;#32;next&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;$1&amp;amp;#32;==&amp;amp;#32;key&amp;amp;#32;{&amp;amp;#32;$2&amp;amp;#32;=&amp;amp;#32;password&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$work/users&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/domains&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$USERS&amp;amp;quot;&amp;amp;#32;dovecot&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Password&amp;amp;#32;changed:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$address&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;add-alias)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;alias=$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target=$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_address&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;${alias#*@}&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;${target#*@}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;and&amp;amp;#32;canonical&amp;amp;#32;user&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;domain.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;existing&amp;amp;#32;canonical&amp;amp;#32;user.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;has_user&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;has_alias&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;;&amp;amp;#32;then&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Alias&amp;amp;#32;address&amp;amp;#32;already&amp;amp;#32;exists.&amp;amp;quot;;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cat&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;%s:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$alias&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$target&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;replace_file&amp;amp;#32;&amp;amp;quot;$work/aliases&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$ALIASES&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$EXIMGROUP&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;regenerate_maps&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;Alias&amp;amp;#32;added:&amp;amp;#32;%s&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;%s&amp;amp;#32;(including&amp;amp;#32;permission&amp;amp;#32;to&amp;amp;#32;send)\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$(normalize&amp;amp;#32;&amp;amp;quot;$2&amp;amp;quot;)&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#39;s&amp;amp;#32;default&amp;amp;#32;authentication&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;disabled.&amp;amp;#32;If&amp;amp;#32;cache&amp;amp;#32;is&amp;amp;#32;enabled&lt;br /&gt;
#&amp;amp;#32;later,&amp;amp;#32;flush&amp;amp;#32;it&amp;amp;#32;after&amp;amp;#32;password&amp;amp;#32;changes;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;services&amp;amp;#32;here.&lt;br /&gt;
exit&amp;amp;#32;0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-user&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-domain&amp;amp;#32;example.net&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;alice@example.net&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;alias&amp;amp;#32;in&amp;amp;#32;root&amp;amp;#32;webmaster&amp;amp;#32;admin&amp;amp;#32;postmaster;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mail-user&amp;amp;#32;add-alias&amp;amp;#32;&amp;quot;$alias@$domain&amp;quot;&amp;amp;#32;&amp;quot;alice@$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;done&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Каждый add-user запрашивает новый пароль дважды. Во время первоначального наполнения службы ещё выключены. Для последующего обслуживания:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail-user&amp;amp;#32;set-password&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-alias&amp;amp;#32;support@example.org&amp;amp;#32;alice@example.org&lt;br /&gt;
mail-user&amp;amp;#32;add-user&amp;amp;#32;bob@example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Алиасы не получают собственного пароля и не используются как независимые IMAP-логины. При стандартном отключённом auth cache смена пароля действует для новых входов без рестарта Dovecot. Уже открытая IMAP-сессия при этом не закрывается.&lt;br /&gt;
&lt;br /&gt;
== 8. DKIM и DNS до запуска ==&lt;br /&gt;
Для каждого домена создать отдельный RSA-2048 ключ с селектором mail. Команда отказывается перезаписывать существующий ключ. Закрытые ключи не копируются в wiki и DNS.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
for&amp;amp;#32;domain&amp;amp;#32;in&amp;amp;#32;example.org&amp;amp;#32;example.net;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;key=/usr/local/etc/exim/dkim/$domain.key&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;||&amp;amp;#32;{&amp;amp;#32;echo&amp;amp;#32;&amp;quot;Key&amp;amp;#32;already&amp;amp;#32;exists:&amp;amp;#32;$domain&amp;quot;&amp;amp;#32;&amp;gt;&amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;genpkey&amp;amp;#32;-algorithm&amp;amp;#32;RSA&amp;amp;#32;-pkeyopt&amp;amp;#32;rsa_keygen_bits:2048&amp;amp;#32;-out&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chown&amp;amp;#32;root:mail&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;&amp;quot;$key&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&amp;quot;DNS&amp;amp;#32;owner:&amp;amp;#32;mail._domainkey.$domain&amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;openssl&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;quot;$key&amp;quot;&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;|&amp;amp;#32;openssl&amp;amp;#32;base64&amp;amp;#32;-A&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&#039;\n&#039;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Опубликовать по одному TXT mail._domainkey в каждой зоне со значением v=DKIM1; k=rsa; p= и соответствующим полученным base64 без пробелов и переносов. В интерфейсе DNS вводится одно TXT-значение; в BIND длинная строка делится на строки не более 255 байт внутри одной записи. Селектор Exim уже настроен на mail.&lt;br /&gt;
&lt;br /&gt;
Начальные примеры для новой инфраструктуры:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mail.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;A&amp;amp;#32;192.0.2.10&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=spf1&amp;amp;#32;ip4:192.0.2.10&amp;amp;#32;~all&amp;quot;&lt;br /&gt;
_dmarc.example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
_dmarc.example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;TXT&amp;amp;#32;&amp;quot;v=DMARC1;&amp;amp;#32;p=none&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если SPF уже существует, объединить разрешённые источники в одной записи; второй SPF не добавлять. DMARC ужесточают после проверки всех легитимных источников. Для уже работающего домена не заменять его текущую политику этими начальными примерами.&lt;br /&gt;
&lt;br /&gt;
MX публикуется в финале после проверки службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
example.org.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
example.net.&amp;amp;#32;300&amp;amp;#32;IN&amp;amp;#32;MX&amp;amp;#32;10&amp;amp;#32;mail.example.org.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 9. Проверки до сертификатов ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bV&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bP&amp;amp;#32;primary_hostname&amp;amp;#32;spool_directory&amp;amp;#32;exim_user&amp;amp;#32;exim_group&lt;br /&gt;
/usr/local/bin/doveconf&amp;amp;#32;-o&amp;amp;#32;ssl=no&amp;amp;#32;-o&amp;amp;#32;ssl_server_cert_file=&amp;amp;#32;-o&amp;amp;#32;ssl_server_key_file=&amp;amp;#32;-n&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;alice@example.org&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;admin@example.net&lt;br /&gt;
/usr/local/sbin/exim&amp;amp;#32;-bt&amp;amp;#32;does-not-exist@example.org&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&#039;$2&amp;amp;#32;==&amp;amp;#32;&amp;quot;/opt&amp;quot;&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Последняя маршрутизация должна завершиться отказом для неизвестного пользователя. ssl=no выше действует только на процесс проверки doveconf и не изменяет рабочий конфиг. Пустые пути сертификатов используются лишь для этой проверки до выпуска. Почтовые службы всё ещё выключены.&lt;br /&gt;
&lt;br /&gt;
== 10. Последний этап базовой установки: сертификат, TLS и запуск ==&lt;br /&gt;
Сначала убедиться, что A mail.example.org существует в публичном DNS. Ручной DNS-01 не требует входящего TCP 80. Если домен имеет CAA, он должен разрешать выбранный центр сертификации. Самоподписанный сертификат для обычных почтовых клиентов здесь не используется.&lt;br /&gt;
&lt;br /&gt;
=== 10.1. Hook проверки и активации ===&lt;br /&gt;
Сохранить следующий скрипт как /usr/local/sbin/mail-activate-tls, root:wheel, 0750. Он проверяет UFS, права, цепочку доверия, имя, срок и соответствие ключа; лишь затем включает и запускает/перечитывает Dovecot и Exim. Если включен Nginx, проверяется и перечитывается он. На первом запуске до появления сертификата hook должен завершаться отказом.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&amp;amp;#32;Activate&amp;amp;#32;or&amp;amp;#32;renew&amp;amp;#32;trusted&amp;amp;#32;TLS&amp;amp;#32;for&amp;amp;#32;mail.example.org.&lt;br /&gt;
#&amp;amp;#32;Suitable&amp;amp;#32;as&amp;amp;#32;an&amp;amp;#32;ACME&amp;amp;#32;reload&amp;amp;#32;hook.&amp;amp;#32;It&amp;amp;#32;never&amp;amp;#32;creates&amp;amp;#32;certificates&amp;amp;#32;or&amp;amp;#32;keys.&lt;br /&gt;
#&amp;amp;#32;All&amp;amp;#32;checks&amp;amp;#32;finish&amp;amp;#32;before&amp;amp;#32;boot&amp;amp;#32;settings&amp;amp;#32;or&amp;amp;#32;running&amp;amp;#32;daemons&amp;amp;#32;are&amp;amp;#32;changed.&lt;br /&gt;
set&amp;amp;#32;-eu&lt;br /&gt;
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin&lt;br /&gt;
export&amp;amp;#32;PATH&lt;br /&gt;
LC_ALL=C&lt;br /&gt;
export&amp;amp;#32;LC_ALL&lt;br /&gt;
umask&amp;amp;#32;077&lt;br /&gt;
&lt;br /&gt;
HOST=mail.example.org&lt;br /&gt;
CERTDIR=/usr/local/etc/mail-certs&lt;br /&gt;
CERT=$CERTDIR/fullchain.pem&lt;br /&gt;
KEY=$CERTDIR/privkey.pem&lt;br /&gt;
CAFILE=/etc/ssl/cert.pem&lt;br /&gt;
EXIM=/usr/local/sbin/exim&lt;br /&gt;
DOVECONF=/usr/local/bin/doveconf&lt;br /&gt;
OPENSSL=/usr/bin/openssl&lt;br /&gt;
SERVICE=/usr/sbin/service&lt;br /&gt;
&lt;br /&gt;
die()&amp;amp;#32;{&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;mail-activate-tls:&amp;amp;#32;%s\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$*&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;&amp;amp;amp;2;&amp;amp;#32;exit&amp;amp;#32;1;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Run&amp;amp;#32;with&amp;amp;#32;sudo&amp;amp;#32;or&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;ACME&amp;amp;#32;hook.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;${1-}&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;--locked)&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$#&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;1&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;;&amp;amp;#32;shift&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#39;&amp;amp;#39;)&amp;amp;#32;exec&amp;amp;#32;/usr/bin/lockf&amp;amp;#32;-t&amp;amp;#32;0&amp;amp;#32;/var/run/mail-activate-tls.lock&amp;amp;#32;&amp;amp;quot;$0&amp;amp;quot;&amp;amp;#32;--locked&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;No&amp;amp;#32;arguments&amp;amp;#32;are&amp;amp;#32;supported.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fail&amp;amp;#32;closed&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;is&amp;amp;#32;unavailable&amp;amp;#32;or&amp;amp;#32;read-only.&lt;br /&gt;
/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#39;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$4&amp;amp;#32;!~&amp;amp;#32;/(^|,)ro(,|$)/&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#39;&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;mounted&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;writable&amp;amp;#32;UFS&amp;amp;#32;filesystem.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-d&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;missing.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Trusted&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;privkey.pem&amp;amp;#32;is&amp;amp;#32;missing,&amp;amp;#32;empty&amp;amp;#32;or&amp;amp;#32;unreadable;&amp;amp;#32;services&amp;amp;#32;were&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Install&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;key&amp;amp;#32;as&amp;amp;#32;real&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;mail-certs,&amp;amp;#32;not&amp;amp;#32;symlinks&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;private&amp;amp;#32;ACME&amp;amp;#32;directory.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-r&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;System&amp;amp;#32;CA&amp;amp;#32;bundle&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
mail_gid=$(pw&amp;amp;#32;groupshow&amp;amp;#32;mail&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;-F:&amp;amp;#32;&amp;amp;#39;NR&amp;amp;#32;==&amp;amp;#32;1&amp;amp;#32;{&amp;amp;#32;print&amp;amp;#32;$3&amp;amp;#32;}&amp;amp;#39;)&lt;br /&gt;
[&amp;amp;#32;-n&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;determine&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;fixed&amp;amp;#32;deployment&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;readable&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;Exim&amp;amp;#32;group.&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:640&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0640.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;&amp;amp;#39;%u:%g:%Lp&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$CERTDIR&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;0:$mail_gid:750&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;directory&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root:mail&amp;amp;#32;with&amp;amp;#32;mode&amp;amp;#32;0750.&amp;amp;quot;&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%u&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;root.&amp;amp;quot;&lt;br /&gt;
case&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%Lp&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;640|440)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(stat&amp;amp;#32;-L&amp;amp;#32;-f&amp;amp;#32;%g&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;$mail_gid&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;A&amp;amp;#32;group-readable-only&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;group.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;644|444)&amp;amp;#32;;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;*)&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;readable&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;group&amp;amp;#32;or&amp;amp;#32;others.&amp;amp;quot;&amp;amp;#32;;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;OpenSSL,&amp;amp;#32;Exim&amp;amp;#32;or&amp;amp;#32;doveconf&amp;amp;#32;is&amp;amp;#32;unavailable.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
work=$(mktemp&amp;amp;#32;-d&amp;amp;#32;/var/run/mail-tls-check.XXXXXX)&lt;br /&gt;
cleanup()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rm&amp;amp;#32;-f&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;&amp;amp;quot;$work&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true&lt;br /&gt;
}&lt;br /&gt;
trap&amp;amp;#32;cleanup&amp;amp;#32;EXIT&lt;br /&gt;
trap&amp;amp;#32;&amp;amp;#39;exit&amp;amp;#32;1&amp;amp;#39;&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Parse&amp;amp;#32;every&amp;amp;#32;certificate&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;supplied&amp;amp;#32;PEM&amp;amp;#32;chain.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;crl2pkcs7&amp;amp;#32;-nocrl&amp;amp;#32;-certfile&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/chain.p7b&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;supplied&amp;amp;#32;fullchain.pem&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;valid&amp;amp;#32;PEM&amp;amp;#32;certificate&amp;amp;#32;chain.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkhost&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;mail.example.org.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-noout&amp;amp;#32;-checkend&amp;amp;#32;0&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;is&amp;amp;#32;expired.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Also&amp;amp;#32;checks&amp;amp;#32;notBefore,&amp;amp;#32;issuer&amp;amp;#32;chain,&amp;amp;#32;trust,&amp;amp;#32;server&amp;amp;#32;purpose&amp;amp;#32;and&amp;amp;#32;hostname.&lt;br /&gt;
#&amp;amp;#32;A&amp;amp;#32;self-signed&amp;amp;#32;or&amp;amp;#32;untrusted&amp;amp;#32;certificate&amp;amp;#32;never&amp;amp;#32;triggers&amp;amp;#32;service&amp;amp;#32;activation.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;verify&amp;amp;#32;-CAfile&amp;amp;#32;&amp;amp;quot;$CAFILE&amp;amp;quot;&amp;amp;#32;-purpose&amp;amp;#32;sslserver&amp;amp;#32;-verify_hostname&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&amp;amp;#32;-untrusted&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Certificate&amp;amp;#32;verification&amp;amp;#32;failed:&amp;amp;#32;check&amp;amp;#32;system&amp;amp;#32;trust,&amp;amp;#32;full&amp;amp;#32;chain,&amp;amp;#32;dates&amp;amp;#32;and&amp;amp;#32;server&amp;amp;#32;name.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Empty&amp;amp;#32;passphrase&amp;amp;#32;prevents&amp;amp;#32;an&amp;amp;#32;unattended&amp;amp;#32;hook&amp;amp;#32;from&amp;amp;#32;blocking&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;prompt.&lt;br /&gt;
#&amp;amp;#32;Nothing&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;printed&amp;amp;#32;or&amp;amp;#32;written&amp;amp;#32;into&amp;amp;#32;temporary&amp;amp;#32;files.&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-check&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;is&amp;amp;#32;invalid,&amp;amp;#32;encrypted&amp;amp;#32;or&amp;amp;#32;unreadable.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$CERT&amp;amp;quot;&amp;amp;#32;-pubkey&amp;amp;#32;-noout&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-pubin&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$work/cert.pub.pem&amp;amp;quot;&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;normalize&amp;amp;#32;the&amp;amp;#32;certificate&amp;amp;#32;public&amp;amp;#32;key.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$OPENSSL&amp;amp;quot;&amp;amp;#32;pkey&amp;amp;#32;-in&amp;amp;#32;&amp;amp;quot;$KEY&amp;amp;quot;&amp;amp;#32;-passin&amp;amp;#32;pass:&amp;amp;#32;-pubout&amp;amp;#32;-outform&amp;amp;#32;DER&amp;amp;#32;-out&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Cannot&amp;amp;#32;derive&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#39;s&amp;amp;#32;public&amp;amp;#32;component.&amp;amp;quot;&lt;br /&gt;
cmp&amp;amp;#32;-s&amp;amp;#32;&amp;amp;quot;$work/cert.pub.der&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$work/key.pub.der&amp;amp;quot;&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;The&amp;amp;#32;certificate&amp;amp;#32;and&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;match.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Read&amp;amp;#32;the&amp;amp;#32;real&amp;amp;#32;installed&amp;amp;#32;production&amp;amp;#32;configurations&amp;amp;#32;with&amp;amp;#32;real&amp;amp;#32;certificates.&lt;br /&gt;
&amp;amp;quot;$EXIM&amp;amp;quot;&amp;amp;#32;-bV&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;quot;$DOVECONF&amp;amp;quot;&amp;amp;#32;-n&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;SnappyMail&amp;amp;#32;shares&amp;amp;#32;this&amp;amp;#32;certificate&amp;amp;#32;through&amp;amp;#32;nginx&amp;amp;#39;s&amp;amp;#32;root&amp;amp;#32;master&amp;amp;#32;process.&lt;br /&gt;
#&amp;amp;#32;Check&amp;amp;#32;nginx&amp;amp;#32;before&amp;amp;#32;reloading&amp;amp;#32;any&amp;amp;#32;certificate&amp;amp;#32;consumer&amp;amp;#32;when&amp;amp;#32;webmail&amp;amp;#32;is&amp;amp;#32;enabled.&lt;br /&gt;
reload_nginx=no&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-x&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(/usr/sbin/sysrc&amp;amp;#32;-n&amp;amp;#32;nginx_enable&amp;amp;#32;2&amp;amp;gt;/dev/null&amp;amp;#32;||&amp;amp;#32;true)&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;YES&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;/usr/local/sbin/nginx&amp;amp;#32;-t&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;nginx&amp;amp;#32;configuration&amp;amp;#32;check&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;reload_nginx=yes&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;daemon,&amp;amp;#32;rc&amp;amp;#32;setting,&amp;amp;#32;certificate&amp;amp;#32;or&amp;amp;#32;key&amp;amp;#32;was&amp;amp;#32;changed&amp;amp;#32;before&amp;amp;#32;this&amp;amp;#32;point.&lt;br /&gt;
/usr/sbin/sysrc&amp;amp;#32;dovecot_enable=YES&amp;amp;#32;exim_enable=YES&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Could&amp;amp;#32;not&amp;amp;#32;enable&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;mail&amp;amp;#32;services&amp;amp;#32;at&amp;amp;#32;boot.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload()&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;srv=$1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onereload&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;reload&amp;amp;#32;failed;&amp;amp;#32;no&amp;amp;#32;forced&amp;amp;#32;restart&amp;amp;#32;was&amp;amp;#32;attempted.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;quot;&amp;amp;#32;onestart&amp;amp;#32;||&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;$srv&amp;amp;#32;start&amp;amp;#32;failed.&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;owns&amp;amp;#32;both&amp;amp;#32;the&amp;amp;#32;SMTP&amp;amp;#32;AUTH&amp;amp;#32;and&amp;amp;#32;LMTP&amp;amp;#32;sockets,&amp;amp;#32;so&amp;amp;#32;it&amp;amp;#32;goes&amp;amp;#32;first.&lt;br /&gt;
start_or_reload&amp;amp;#32;dovecot&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;dovecot&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/auth-exim&amp;amp;#32;]&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#32;-S&amp;amp;#32;/var/run/dovecot/lmtp-exim&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Dovecot&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready&amp;amp;#32;with&amp;amp;#32;both&amp;amp;#32;Exim&amp;amp;#32;sockets;&amp;amp;#32;Exim&amp;amp;#32;was&amp;amp;#32;not&amp;amp;#32;changed.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
start_or_reload&amp;amp;#32;exim&lt;br /&gt;
ready=no&lt;br /&gt;
for&amp;amp;#32;attempt&amp;amp;#32;in&amp;amp;#32;1&amp;amp;#32;2&amp;amp;#32;3&amp;amp;#32;4&amp;amp;#32;5;&amp;amp;#32;do&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;$SERVICE&amp;amp;quot;&amp;amp;#32;exim&amp;amp;#32;onestatus&amp;amp;#32;&amp;amp;gt;/dev/null&amp;amp;#32;2&amp;amp;gt;&amp;amp;amp;1;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ready=yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sleep&amp;amp;#32;1&lt;br /&gt;
done&lt;br /&gt;
[&amp;amp;#32;&amp;amp;quot;$ready&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;die&amp;amp;#32;&amp;amp;quot;Exim&amp;amp;#32;did&amp;amp;#32;not&amp;amp;#32;become&amp;amp;#32;ready.&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;start_or_reload&amp;amp;#32;nginx&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
printf&amp;amp;#32;&amp;amp;#39;Trusted&amp;amp;#32;TLS&amp;amp;#32;activated&amp;amp;#32;or&amp;amp;#32;reloaded&amp;amp;#32;for&amp;amp;#32;%s;&amp;amp;#32;Dovecot&amp;amp;#32;and&amp;amp;#32;Exim&amp;amp;#32;are&amp;amp;#32;running.\n&amp;amp;#39;&amp;amp;#32;&amp;amp;quot;$HOST&amp;amp;quot;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$reload_nginx&amp;amp;quot;&amp;amp;#32;=&amp;amp;#32;yes&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;printf&amp;amp;#32;&amp;amp;#39;SnappyMail&amp;amp;#32;nginx&amp;amp;#32;also&amp;amp;#32;reloaded&amp;amp;#32;with&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;certificate.\n&amp;amp;#39;&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/sbin/mail-activate-tls&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 10.2. Ручной выпуск DNS-01 ===&lt;br /&gt;
Выбрать свой реальный административный email. ACME хранится в отдельном root-only каталоге. Команды ниже вводятся на сервере от root; выданный TXT берется из фактического ответа acme.sh, а не из чужой инструкции.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/usr/local/etc/virtual-mail/acme&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--register-account&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;-m&amp;amp;#32;postmaster@example.org&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--issue&amp;amp;#32;--server&amp;amp;#32;letsencrypt&amp;amp;#32;--dns&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--keylength&amp;amp;#32;ec-256&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Создать TXT с именем _acme-challenge.mail.example.org и значением, показанным acme.sh. Дождаться ответа авторитетных DNS, затем подтвердить проверку:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--renew&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--yes-I-know-dns-manual-mode-enough-go-ahead-please&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После успешного выпуска установить копии сертификата в рабочий каталог. Hook выставляет права до строгой проверки:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
acme.sh&amp;amp;#32;--home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--config-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme&amp;amp;#32;--cert-home&amp;amp;#32;/usr/local/etc/virtual-mail/acme/certs&amp;amp;#32;--install-cert&amp;amp;#32;-d&amp;amp;#32;mail.example.org&amp;amp;#32;--ecc&amp;amp;#32;--fullchain-file&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;--key-file&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;--reloadcmd&amp;amp;#32;&#039;chown&amp;amp;#32;root:mail&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;chmod&amp;amp;#32;0640&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;&amp;amp;&amp;amp;#32;/usr/local/sbin/mail-activate-tls&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ручной DNS-01 &#039;&#039;&#039;не обеспечивает автоматическое продление&#039;&#039;&#039;. До истечения срока повторить процедуру, опубликовав новое значение TXT; после успешного продления снова выполнить install-cert. Для автоматизации впоследствии перейти на DNS API с минимально необходимыми правами. Один сертификат mail.example.org используется для всех ящиков; почтовые клиенты всех доменов подключаются именно к этому имени.&lt;br /&gt;
&lt;br /&gt;
=== 10.3. Проверка клиента и переключение MX ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-46l&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:993&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:465&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
openssl&amp;amp;#32;s_client&amp;amp;#32;-starttls&amp;amp;#32;smtp&amp;amp;#32;-connect&amp;amp;#32;mail.example.org:587&amp;amp;#32;-servername&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_hostname&amp;amp;#32;mail.example.org&amp;amp;#32;-verify_return_error&amp;amp;#32;-CAfile&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После просмотра TLS нажать Ctrl-D. Проверить реальный вход alice@example.org и alice@example.net: IMAP mail.example.org:993 SSL/TLS; SMTP mail.example.org:465 SSL/TLS либо 587 STARTTLS, обязательный AUTH и полный логин. Неверный пароль и relay без AUTH должны отклоняться. Проверить доставку на канонический адрес и алиас, отсутствие неизвестных получателей; затем опубликовать MX из раздела DNS. Проверка из LAN через публичный IP может использовать NAT reflection: внешнюю доступность отдельно проверяют из независимой сети.&lt;br /&gt;
&lt;br /&gt;
Для локальной системной почты после проверки настроить /etc/mail/mailer.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
send-mail&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
mailq&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
newaliases&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
hoststat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
purgestat&amp;amp;#32;/usr/local/sbin/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Локальный sendmail-совместимый вызов и SMTP AUTH — разные пути. Приведенные проверки отправителя в ACL относятся к SMTP.&lt;br /&gt;
== 11. Необязательно: SnappyMail на этом же сервере ==&lt;br /&gt;
Этот этап выполняется после запуска почты с доверенным TLS. Nginx обслуживает веб-клиент, отдельный PHP-FPM pool работает от snappymail. Пароли почты и Maildir веб-процессу не передаются как файлы: он использует IMAPS/SMTP AUTH вошедшего пользователя.&lt;br /&gt;
&lt;br /&gt;
=== 11.1. Пакеты и каталоги ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;snappymail-php84&amp;amp;#32;nginx&amp;amp;#32;php84-opcache&lt;br /&gt;
sysrc&amp;amp;#32;nginx_enable=NO&amp;amp;#32;php_fpm_enable=NO&lt;br /&gt;
pw&amp;amp;#32;groupadd&amp;amp;#32;snappymail&lt;br /&gt;
pw&amp;amp;#32;useradd&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-d&amp;amp;#32;/nonexistent&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/webmail&amp;amp;#32;/opt/webmail/nginx&amp;amp;#32;/opt/webmail/snappymail&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;www&amp;amp;#32;-g&amp;amp;#32;www&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/fastcgi&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/nginx/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/webmail/snappymail/log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;snappymail&amp;amp;#32;-g&amp;amp;#32;snappymail&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Команды создания пользователя и пустого журнала относятся к первой установке. При повторном выполнении проверять существующие записи и не обнулять журналы.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/www/snappymail/include.php как root:wheel 0644:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
define(&#039;APP_DATA_FOLDER_PATH&#039;,&amp;amp;#32;&#039;/opt/webmail/snappymail/data/&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Код /usr/local/www/snappymail остается root-owned. Не делать его доступным на запись PHP-пользователю.&lt;br /&gt;
&lt;br /&gt;
В /etc/hosts добавить единственное разрешение имени mail.example.org в 127.0.0.1, предварительно исправив конфликтующие записи этого имени:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
127.0.0.1&amp;amp;#32;mail.example.org&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Это сохраняет локальный маршрут и проверку TLS по FQDN.&lt;br /&gt;
&lt;br /&gt;
=== 11.2. PHP-FPM и PHP ===&lt;br /&gt;
Установить php.ini-production как /usr/local/etc/php.ini, если своего файла ещё нет. Сохранить /usr/local/etc/php-fpm.conf (все конфиги PHP/Nginx ниже — root:wheel 0644):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[global]&lt;br /&gt;
pid&amp;amp;#32;=&amp;amp;#32;/var/run/php-fpm.pid&lt;br /&gt;
error_log&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-fpm-master.log&lt;br /&gt;
log_level&amp;amp;#32;=&amp;amp;#32;notice&lt;br /&gt;
include&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/php-fpm.d/snappymail.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полный pool /usr/local/etc/php-fpm.d/snappymail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[snappymail]&lt;br /&gt;
user&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
group&amp;amp;#32;=&amp;amp;#32;snappymail&lt;br /&gt;
listen&amp;amp;#32;=&amp;amp;#32;/var/run/snappymail-php-fpm.sock&lt;br /&gt;
listen.owner&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.group&amp;amp;#32;=&amp;amp;#32;www&lt;br /&gt;
listen.mode&amp;amp;#32;=&amp;amp;#32;0660&lt;br /&gt;
pm&amp;amp;#32;=&amp;amp;#32;ondemand&lt;br /&gt;
pm.max_children&amp;amp;#32;=&amp;amp;#32;4&lt;br /&gt;
pm.process_idle_timeout&amp;amp;#32;=&amp;amp;#32;30s&lt;br /&gt;
pm.max_requests&amp;amp;#32;=&amp;amp;#32;500&lt;br /&gt;
chdir&amp;amp;#32;=&amp;amp;#32;/usr/local/www/snappymail&lt;br /&gt;
clear_env&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
catch_workers_output&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
security.limit_extensions&amp;amp;#32;=&amp;amp;#32;.php&lt;br /&gt;
env[TMPDIR]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[upload_tmp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[sys_temp_dir]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/tmp&lt;br /&gt;
php_admin_value[session.save_path]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/sessions&lt;br /&gt;
php_admin_value[error_log]&amp;amp;#32;=&amp;amp;#32;/opt/webmail/snappymail/log/php-error.log&lt;br /&gt;
php_admin_flag[log_errors]&amp;amp;#32;=&amp;amp;#32;on&lt;br /&gt;
php_admin_flag[display_errors]&amp;amp;#32;=&amp;amp;#32;off&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Сохранить /usr/local/etc/php/99-snappymail.ini:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
date.timezone&amp;amp;#32;=&amp;amp;#32;Europe/Moscow&lt;br /&gt;
expose_php&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
display_errors&amp;amp;#32;=&amp;amp;#32;Off&lt;br /&gt;
log_errors&amp;amp;#32;=&amp;amp;#32;On&lt;br /&gt;
memory_limit&amp;amp;#32;=&amp;amp;#32;128M&lt;br /&gt;
max_execution_time&amp;amp;#32;=&amp;amp;#32;90&lt;br /&gt;
post_max_size&amp;amp;#32;=&amp;amp;#32;32M&lt;br /&gt;
upload_max_filesize&amp;amp;#32;=&amp;amp;#32;25M&lt;br /&gt;
session.cookie_secure&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_httponly&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
session.cookie_samesite&amp;amp;#32;=&amp;amp;#32;Lax&lt;br /&gt;
cgi.fix_pathinfo&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
openssl.cafile&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
curl.cainfo&amp;amp;#32;=&amp;amp;#32;/etc/ssl/cert.pem&lt;br /&gt;
opcache.enable&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.memory_consumption&amp;amp;#32;=&amp;amp;#32;64&lt;br /&gt;
opcache.interned_strings_buffer&amp;amp;#32;=&amp;amp;#32;8&lt;br /&gt;
opcache.max_accelerated_files&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
opcache.validate_timestamps&amp;amp;#32;=&amp;amp;#32;1&lt;br /&gt;
opcache.revalidate_freq&amp;amp;#32;=&amp;amp;#32;60&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/php_fpm:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/snappymail/data&amp;amp;#32;/opt/webmail/snappymail/tmp&amp;amp;#32;/opt/webmail/snappymail/sessions&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 11.3. Инициализация приложения без публикации пароля ===&lt;br /&gt;
Сохранить следующий одноразовый PHP-скрипт в /root/bootstrap-webmail.php (root:wheel, 0600). Он предназначен для новой установки и отказывается заменять существующую конфигурацию. Запустить интерактивно с отключенным эхом терминала; задать новый длинный уникальный пароль администратора дважды. Сам пароль в файл или аргументы не записывается.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;lt;?php&lt;br /&gt;
declare(strict_types=1);&lt;br /&gt;
umask(0077);&lt;br /&gt;
$base&amp;amp;#32;=&amp;amp;#32;&#039;/opt/webmail/snappymail/data/_data_/_default_&#039;;&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/configs/application.ini&#039;))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Already&amp;amp;#32;configured;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;reset&amp;amp;#32;it.&#039;);&lt;br /&gt;
}&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;New&amp;amp;#32;SnappyMail&amp;amp;#32;administrator&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$first&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\nRepeat&amp;amp;#32;password:&amp;amp;#32;&amp;quot;);&lt;br /&gt;
$second&amp;amp;#32;=&amp;amp;#32;rtrim((string)fgets(STDIN),&amp;amp;#32;&amp;quot;\r\n&amp;quot;);&lt;br /&gt;
fwrite(STDERR,&amp;amp;#32;&amp;quot;\n&amp;quot;);&lt;br /&gt;
if&amp;amp;#32;(strlen($first)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;20&amp;amp;#32;||&amp;amp;#32;!hash_equals($first,&amp;amp;#32;$second))&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Passwords&amp;amp;#32;differ&amp;amp;#32;or&amp;amp;#32;are&amp;amp;#32;too&amp;amp;#32;short.&#039;);&lt;br /&gt;
}&lt;br /&gt;
$_ENV[&#039;SNAPPYMAIL_INCLUDE_AS_API&#039;]&amp;amp;#32;=&amp;amp;#32;1;&lt;br /&gt;
require&amp;amp;#32;&#039;/usr/local/www/snappymail/index.php&#039;;&lt;br /&gt;
$config&amp;amp;#32;=&amp;amp;#32;\RainLoop\Api::Config();&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;title&#039;,&amp;amp;#32;&#039;Webmail&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;language&#039;,&amp;amp;#32;&#039;ru&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;webmail&#039;,&amp;amp;#32;&#039;attachment_size_limit&#039;,&amp;amp;#32;25);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;allow_admin_panel&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;admin_login&#039;,&amp;amp;#32;&#039;admin&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;security&#039;,&amp;amp;#32;&#039;force_https&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;admin_panel&#039;,&amp;amp;#32;&#039;allow_update&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;verify_certificate&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;allow_self_signed&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;ssl&#039;,&amp;amp;#32;&#039;cafile&#039;,&amp;amp;#32;&#039;/etc/ssl/cert.pem&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;enable&#039;,&amp;amp;#32;true);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;type&#039;,&amp;amp;#32;&#039;sqlite&#039;);&lt;br /&gt;
$config-&amp;amp;gt;Set(&#039;contacts&#039;,&amp;amp;#32;&#039;sqlite_global&#039;,&amp;amp;#32;false);&lt;br /&gt;
$config-&amp;amp;gt;SetPassword(new&amp;amp;#32;\SnappyMail\SensitiveString($first));&lt;br /&gt;
if&amp;amp;#32;(!$config-&amp;amp;gt;Save())&amp;amp;#32;{&amp;amp;#32;throw&amp;amp;#32;new&amp;amp;#32;RuntimeException(&#039;Save&amp;amp;#32;failed.&#039;);&amp;amp;#32;}&lt;br /&gt;
unset($first,&amp;amp;#32;$second);&lt;br /&gt;
$ssl&amp;amp;#32;=&amp;amp;#32;[&#039;verify_peer&#039;=&amp;amp;gt;true,&amp;amp;#32;&#039;verify_peer_name&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;allow_self_signed&#039;=&amp;amp;gt;false,&amp;amp;#32;&#039;cafile&#039;=&amp;amp;gt;&#039;/etc/ssl/cert.pem&#039;];&lt;br /&gt;
$settings&amp;amp;#32;=&amp;amp;#32;[&lt;br /&gt;
&amp;amp;#32;&#039;IMAP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;993,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;SMTP&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;465,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;shortLogin&#039;=&amp;amp;gt;false,&#039;lowerLogin&#039;=&amp;amp;gt;true,&#039;useAuth&#039;=&amp;amp;gt;true,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;setSender&#039;=&amp;amp;gt;false,&#039;usePhpMail&#039;=&amp;amp;gt;false,&#039;authPlainLine&#039;=&amp;amp;gt;false,&#039;ssl&#039;=&amp;amp;gt;$ssl],&lt;br /&gt;
&amp;amp;#32;&#039;Sieve&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;[&#039;host&#039;=&amp;amp;gt;&#039;mail.example.org&#039;,&#039;port&#039;=&amp;amp;gt;4190,&#039;type&#039;=&amp;amp;gt;1,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&#039;enabled&#039;=&amp;amp;gt;false,&#039;shortLogin&#039;=&amp;amp;gt;false],&lt;br /&gt;
&amp;amp;#32;&#039;whiteList&#039;&amp;amp;#32;=&amp;amp;gt;&amp;amp;#32;&#039;&#039;&lt;br /&gt;
];&lt;br /&gt;
$dir&amp;amp;#32;=&amp;amp;#32;$base.&#039;/domains&#039;;&lt;br /&gt;
if&amp;amp;#32;(!is_dir($dir))&amp;amp;#32;{&amp;amp;#32;mkdir($dir,&amp;amp;#32;0700,&amp;amp;#32;true);&amp;amp;#32;}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;default.json&#039;,&#039;gmail.com.json&#039;,&#039;hotmail.com.json&#039;,&#039;mail.example.org.json&#039;,&#039;disabled&#039;]&amp;amp;#32;as&amp;amp;#32;$sample)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(is_file($dir.&#039;/&#039;.$sample))&amp;amp;#32;{&amp;amp;#32;unlink($dir.&#039;/&#039;.$sample);&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
foreach&amp;amp;#32;([&#039;example.org&#039;,&#039;example.net&#039;]&amp;amp;#32;as&amp;amp;#32;$domain)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;\RainLoop\Model\Domain::fromArray($domain,&amp;amp;#32;$settings);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;file_put_contents($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json_encode($settings,&amp;amp;#32;JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_THROW_ON_ERROR).&amp;quot;\n&amp;quot;);&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;chmod($dir.&#039;/&#039;.$domain.&#039;.json&#039;,&amp;amp;#32;0600);&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;(is_file($base.&#039;/admin_password.txt&#039;))&amp;amp;#32;{&amp;amp;#32;unlink($base.&#039;/admin_password.txt&#039;);&amp;amp;#32;}&lt;br /&gt;
echo&amp;amp;#32;&amp;quot;Configured.\n&amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Выполнить в интерактивном /bin/sh:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php&amp;amp;#32;-l&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
stty&amp;amp;#32;-echo&lt;br /&gt;
trap&amp;amp;#32;&#039;stty&amp;amp;#32;echo&#039;&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
php&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
result=$?&lt;br /&gt;
stty&amp;amp;#32;echo&lt;br /&gt;
trap&amp;amp;#32;-&amp;amp;#32;EXIT&amp;amp;#32;HUP&amp;amp;#32;INT&amp;amp;#32;TERM&lt;br /&gt;
test&amp;amp;#32;&amp;quot;$result&amp;quot;&amp;amp;#32;-eq&amp;amp;#32;0&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;&amp;quot;$result&amp;quot;&lt;br /&gt;
chown&amp;amp;#32;-R&amp;amp;#32;snappymail:snappymail&amp;amp;#32;/opt/webmail/snappymail/data&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;d&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0700&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
find&amp;amp;#32;/opt/webmail/snappymail/data&amp;amp;#32;-type&amp;amp;#32;f&amp;amp;#32;-exec&amp;amp;#32;chmod&amp;amp;#32;0600&amp;amp;#32;{}&amp;amp;#32;+&lt;br /&gt;
rm&amp;amp;#32;/root/bootstrap-webmail.php&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При ошибке не запускать веб-сервер до исправления и проверки настроек администратора. Панель после запуска — https://mail.example.org/?admin, логин admin, заданный вами пароль. В FreeBSD регистр имени API важен: используется RainLoop\Api, не RainLoop\API.&lt;br /&gt;
&lt;br /&gt;
=== 11.4. Nginx и запуск ===&lt;br /&gt;
Полный минимальный /usr/local/etc/nginx/nginx.conf для сервера, на котором еще нет других сайтов:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
user&amp;amp;#32;www;&lt;br /&gt;
worker_processes&amp;amp;#32;auto;&lt;br /&gt;
pid&amp;amp;#32;/var/run/nginx.pid;&lt;br /&gt;
error_log&amp;amp;#32;/opt/webmail/nginx/log/error.log&amp;amp;#32;warn;&lt;br /&gt;
&lt;br /&gt;
events&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;worker_connections&amp;amp;#32;1024;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
http&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;mime.types;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;default_type&amp;amp;#32;application/octet-stream;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_tokens&amp;amp;#32;off;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;access_log&amp;amp;#32;/opt/webmail/nginx/log/access.log;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sendfile&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tcp_nopush&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;keepalive_timeout&amp;amp;#32;65;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_body_temp_path&amp;amp;#32;/opt/webmail/nginx/client_body;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_temp_path&amp;amp;#32;/opt/webmail/nginx/fastcgi;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_vary&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_min_length&amp;amp;#32;1024;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;gzip_types&amp;amp;#32;text/plain&amp;amp;#32;text/css&amp;amp;#32;application/javascript&amp;amp;#32;application/json&amp;amp;#32;image/svg+xml;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;127.0.0.1:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;198.51.100.20:443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_cache&amp;amp;#32;shared:SSL:4m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_timeout&amp;amp;#32;1h;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_session_tickets&amp;amp;#32;off;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;/usr/local/www/snappymail;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;index&amp;amp;#32;index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Strict-Transport-Security&amp;amp;#32;&amp;amp;quot;max-age=15552000&amp;amp;quot;&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Content-Type-Options&amp;amp;#32;nosniff&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;X-Frame-Options&amp;amp;#32;SAMEORIGIN&amp;amp;#32;always;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;Referrer-Policy&amp;amp;#32;no-referrer&amp;amp;#32;always;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/data&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;^~&amp;amp;#32;/data/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/_include.php&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;(^|/)\.&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~&amp;amp;#32;^/snappymail/v/[^/]+/(app|vendor)/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/webmail/&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;302&amp;amp;#32;/;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try_files&amp;amp;#32;$uri&amp;amp;#32;$uri/&amp;amp;#32;/index.php?$query_string;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;=&amp;amp;#32;/index.php&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;include&amp;amp;#32;fastcgi_params;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;SCRIPT_FILENAME&amp;amp;#32;$document_root/index.php;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTPS&amp;amp;#32;$https&amp;amp;#32;if_not_empty;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_param&amp;amp;#32;HTTP_PROXY&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_pass&amp;amp;#32;unix:/var/run/snappymail-php-fpm.sock;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_hide_header&amp;amp;#32;X-Powered-By;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fastcgi_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;~*&amp;amp;#32;\.php(?:/|$)&amp;amp;#32;{&amp;amp;#32;return&amp;amp;#32;404;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Если сайты уже есть, добавить server-блоки в существующий http, согласовав глобальные параметры. В /etc/rc.conf.d/nginx:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/webmail/nginx/client_body&amp;amp;#32;/opt/webmail/nginx/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить и включить службы:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
php-fpm&amp;amp;#32;-t&lt;br /&gt;
nginx&amp;amp;#32;-t&lt;br /&gt;
sysrc&amp;amp;#32;php_fpm_enable=YES&amp;amp;#32;nginx_enable=YES&lt;br /&gt;
service&amp;amp;#32;php_fpm&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;nginx&amp;amp;#32;start&lt;br /&gt;
fetch&amp;amp;#32;-o&amp;amp;#32;/dev/null&amp;amp;#32;https://mail.example.org/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить в браузере вход полным адресом, список папок, отправку самому себе и наличие письма в INBOX/Sent. Файлы данных, include.php и произвольные .php должны возвращать 404. При добавлении почтового домена через mail-user также добавить его в SnappyMail через админ-панель с теми же параметрами IMAP/SMTP и полной формой логина.&lt;br /&gt;
&lt;br /&gt;
=== 11.5. Если публичный 443 занимает другой Nginx ===&lt;br /&gt;
На внешнем Nginx добавить следующие server-блоки внутри http. Использовать доверенный сертификат mail.example.org; приватный ключ на frontend принадлежит root, режим 0600. Сертификат нужно обновлять на обоих серверах при продлении. HTTPS от frontend до backend тоже проверяется:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Add&amp;amp;#32;these&amp;amp;#32;server&amp;amp;#32;blocks&amp;amp;#32;inside&amp;amp;#32;the&amp;amp;#32;FRONT&amp;amp;#32;nginx&amp;amp;#32;http&amp;amp;#32;{}&amp;amp;#32;context.&lt;br /&gt;
#&amp;amp;#32;Copy&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;mail.example.org&amp;amp;#32;certificate&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;MAIL&amp;amp;#32;server:&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem&lt;br /&gt;
#&amp;amp;#32;/usr/local/etc/mail-certs/privkey.pem&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;-&amp;amp;gt;&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem&lt;br /&gt;
#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;private&amp;amp;#32;key&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600,&amp;amp;#32;on&amp;amp;#32;the&amp;amp;#32;front&amp;amp;#32;server.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;front&amp;amp;#32;nginx&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;able&amp;amp;#32;to&amp;amp;#32;reach&amp;amp;#32;198.51.100.20&amp;amp;#32;TCP&amp;amp;#32;443.&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;80;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;308&amp;amp;#32;https://mail.example.org$request_uri;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
server&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listen&amp;amp;#32;443&amp;amp;#32;ssl;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;server_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/fullchain.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_certificate_key&amp;amp;#32;/usr/local/etc/nginx/ssl/mail.example.org/privkey.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ssl_protocols&amp;amp;#32;TLSv1.2&amp;amp;#32;TLSv1.3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;client_max_body_size&amp;amp;#32;32m;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;location&amp;amp;#32;/&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_pass&amp;amp;#32;https://198.51.100.20;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_http_version&amp;amp;#32;1.1;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Host&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Real-IP&amp;amp;#32;$remote_addr;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-For&amp;amp;#32;$proxy_add_x_forwarded_for;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;X-Forwarded-Proto&amp;amp;#32;https;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_set_header&amp;amp;#32;Connection&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_server_name&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_name&amp;amp;#32;mail.example.org;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify&amp;amp;#32;on;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;system&amp;amp;#32;CA&amp;amp;#32;bundle;&amp;amp;#32;on&amp;amp;#32;Debian/Ubuntu&amp;amp;#32;use&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;/etc/ssl/certs/ca-certificates.crt&amp;amp;#32;if&amp;amp;#32;/etc/ssl/cert.pem&amp;amp;#32;is&amp;amp;#32;absent.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_trusted_certificate&amp;amp;#32;/etc/ssl/cert.pem;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_ssl_verify_depth&amp;amp;#32;3;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_connect_timeout&amp;amp;#32;10s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_read_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_send_timeout&amp;amp;#32;90s;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffer_size&amp;amp;#32;32k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;proxy_buffers&amp;amp;#32;16&amp;amp;#32;16k;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Перед reload выполнить nginx -t. В этой схеме NAT 443 указывает на frontend, а он должен достигать 198.51.100.20:443 по LAN. Порты SMTP/IMAP идут непосредственно на почтовый сервер.&lt;br /&gt;
&lt;br /&gt;
== 12. Необязательно: ClamAV и проверка SMTP-писем ==&lt;br /&gt;
Антивирус подключается после проверки базовой доставки. Exim передает содержимое clamd потоком по loopback; сканеру не нужен доступ к Maildir, очереди и закрытым ключам.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;clamav&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;clamav&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=NO&amp;amp;#32;clamav_freshclam_enable=NO&lt;br /&gt;
#&amp;amp;#32;Пакет&amp;amp;#32;может&amp;amp;#32;включить&amp;amp;#32;clamav&amp;amp;#32;в&amp;amp;#32;дополнительную&amp;amp;#32;группу&amp;amp;#32;mail.&lt;br /&gt;
#&amp;amp;#32;Для&amp;amp;#32;потоковой&amp;amp;#32;проверки&amp;amp;#32;это&amp;amp;#32;не&amp;amp;#32;нужно:&lt;br /&gt;
case&amp;amp;#32;&amp;quot;&amp;amp;#32;$(id&amp;amp;#32;-Gn&amp;amp;#32;clamav)&amp;amp;#32;&amp;quot;&amp;amp;#32;in&amp;amp;#32;*&#039;&amp;amp;#32;mail&amp;amp;#32;&#039;*)&amp;amp;#32;pw&amp;amp;#32;groupmod&amp;amp;#32;mail&amp;amp;#32;-d&amp;amp;#32;clamav&amp;amp;#32;;;&amp;amp;#32;esac&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav&amp;amp;#32;/opt/clamav/log&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/run/clamav&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
install&amp;amp;#32;-o&amp;amp;#32;clamav&amp;amp;#32;-g&amp;amp;#32;clamav&amp;amp;#32;-m&amp;amp;#32;0640&amp;amp;#32;/dev/null&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Штатные rc-скрипты FreeBSD проверяют базы в /var/db/clamav. Для новой установки пустой каталог можно заменить ссылкой; существующие базы не удалять:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;test&amp;amp;#32;&amp;quot;$(readlink&amp;amp;#32;/var/db/clamav)&amp;quot;&amp;amp;#32;=&amp;amp;#32;/opt/clamav/db&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;-d&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rmdir&amp;amp;#32;/var/db/clamav&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
elif&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/var/db/clamav&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;ln&amp;amp;#32;-s&amp;amp;#32;/opt/clamav/db&amp;amp;#32;/var/db/clamav&lt;br /&gt;
else&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;echo&amp;amp;#32;&#039;Unexpected&amp;amp;#32;database&amp;amp;#32;path&#039;&amp;amp;#32;&amp;gt;&amp;amp;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
fi&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/clamav&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/clamd.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
LogFile&amp;amp;#32;/opt/clamav/log/clamd.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/clamd.pid&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
TemporaryDirectory&amp;amp;#32;/opt/clamav/tmp&lt;br /&gt;
User&amp;amp;#32;clamav&lt;br /&gt;
TCPSocket&amp;amp;#32;3310&lt;br /&gt;
TCPAddr&amp;amp;#32;127.0.0.1&lt;br /&gt;
EnableShutdownCommand&amp;amp;#32;no&lt;br /&gt;
MaxThreads&amp;amp;#32;4&lt;br /&gt;
MaxConnectionQueueLength&amp;amp;#32;30&lt;br /&gt;
ReadTimeout&amp;amp;#32;120&lt;br /&gt;
CommandReadTimeout&amp;amp;#32;30&lt;br /&gt;
StreamMaxLength&amp;amp;#32;64M&lt;br /&gt;
MaxFileSize&amp;amp;#32;64M&lt;br /&gt;
MaxScanSize&amp;amp;#32;256M&lt;br /&gt;
MaxScanTime&amp;amp;#32;45000&lt;br /&gt;
MaxRecursion&amp;amp;#32;17&lt;br /&gt;
MaxFiles&amp;amp;#32;10000&lt;br /&gt;
AlertExceedsMax&amp;amp;#32;yes&lt;br /&gt;
ScanMail&amp;amp;#32;yes&lt;br /&gt;
ScanArchive&amp;amp;#32;yes&lt;br /&gt;
SelfCheck&amp;amp;#32;3600&lt;br /&gt;
ConcurrentDatabaseReload&amp;amp;#32;yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
/usr/local/etc/freshclam.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DatabaseDirectory&amp;amp;#32;/opt/clamav/db&lt;br /&gt;
DatabaseOwner&amp;amp;#32;clamav&lt;br /&gt;
UpdateLogFile&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
LogFileMaxSize&amp;amp;#32;0&lt;br /&gt;
LogTime&amp;amp;#32;yes&lt;br /&gt;
LogRotate&amp;amp;#32;no&lt;br /&gt;
LogSyslog&amp;amp;#32;yes&lt;br /&gt;
LogFacility&amp;amp;#32;LOG_DAEMON&lt;br /&gt;
PidFile&amp;amp;#32;/var/run/clamav/freshclam.pid&lt;br /&gt;
DatabaseMirror&amp;amp;#32;database.clamav.net&lt;br /&gt;
ScriptedUpdates&amp;amp;#32;yes&lt;br /&gt;
Checks&amp;amp;#32;12&lt;br /&gt;
ConnectTimeout&amp;amp;#32;30&lt;br /&gt;
ReceiveTimeout&amp;amp;#32;90&lt;br /&gt;
TestDatabases&amp;amp;#32;yes&lt;br /&gt;
NotifyClamd&amp;amp;#32;/usr/local/etc/clamd.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В /etc/rc.conf.d/clamav_clamd и /etc/rc.conf.d/clamav_freshclam добавить по строке:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
required_dirs=&amp;quot;/opt/clamav/db&amp;amp;#32;/opt/clamav/tmp&amp;amp;#32;/opt/clamav/log&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Первую загрузку баз выполнить до запуска clamd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
freshclam&amp;amp;#32;--config-file=/usr/local/etc/freshclam.conf&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/main.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/daily.cvd&lt;br /&gt;
sigtool&amp;amp;#32;--info&amp;amp;#32;/opt/clamav/db/bytecode.cvd&lt;br /&gt;
sysrc&amp;amp;#32;clamav_clamd_enable=YES&amp;amp;#32;clamav_freshclam_enable=YES&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_freshclam&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;clamav_clamd&amp;amp;#32;status&lt;br /&gt;
sockstat&amp;amp;#32;-4l&amp;amp;#32;|&amp;amp;#32;grep&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
При первом bootstrap сообщение NotifyClamd о еще не запущенном clamd допустимо только после успешной загрузки и проверки баз. До подключения Exim clamd должен работать. Checks 12 означает проверку обновлений 12 раз в сутки. Если официальный CDN отвечает 403/429, не повторять запросы в цикле: разобраться с ограничением или использовать подходящее HTTPS-зеркало официальных подписанных баз. Например DatabaseMirror https://mirror.truenetwork.ru/clamav и ScriptedUpdates no загружают полные CVD; сохраняются DNS-проверка актуальности и проверка подписи. Доступность зеркала и свежесть баз проверяются в момент установки.&lt;br /&gt;
&lt;br /&gt;
В главной части конфигурации Exim, до begin acl, добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
av_scanner&amp;amp;#32;=&amp;amp;#32;clamd:127.0.0.1&amp;amp;#32;3310&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
В acl_check_data после существующих проверок From и &#039;&#039;&#039;перед&#039;&#039;&#039; завершающим accept добавить:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;deny&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;malware&amp;amp;#32;=&amp;amp;#32;*&amp;amp;#32;/&amp;amp;#32;tmo=60s&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;Message&amp;amp;#32;rejected&amp;amp;#32;by&amp;amp;#32;antivirus&amp;amp;#32;policy&amp;amp;#32;($malware_name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_message&amp;amp;#32;=&amp;amp;#32;ClamAV&amp;amp;#32;detected&amp;amp;#32;$malware_name&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Затем:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;av_scanner&lt;br /&gt;
tail&amp;amp;#32;-20&amp;amp;#32;/opt/clamav/log/freshclam.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Обнаруженное содержимое отклоняется после DATA с 5xx, включая AUTH-порты 465/587 и SnappyMail. Без defer_ok сбой сканера дает временный 451. AlertExceedsMax yes делает превышение лимитов видимым как Heuristics.Limits.Exceeded. Существующая локальная отправка через sendmail-совместимый вызов не проходит SMTP DATA ACL; ее отдельно учитывают при расширении политики.&lt;br /&gt;
&lt;br /&gt;
Проверить контролируемым письмом обычную доставку, затем стандартным тестом EICAR и ZIP с ним: вредоносное тестовое вложение не должно попасть в ящик. Использовать отдельный тестовый ящик и удалить только его тестовые данные. Сам EICAR брать с официального сайта; не включать его тело в общие журналы и документацию.&lt;br /&gt;
&lt;br /&gt;
=== 12.1. Ротация журналов ===&lt;br /&gt;
Установить newsyslog-правила, чтобы журналы не заполнили UFS. Для Exim пример /usr/local/etc/newsyslog.conf.d/virtual-mail.conf:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/rejectlog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/var/log/exim/paniclog&amp;amp;#32;mailnull:mail&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&lt;br /&gt;
/opt/clamav/log/clamd.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/clamd.pid&amp;amp;#32;1&lt;br /&gt;
/opt/clamav/log/freshclam.log&amp;amp;#32;clamav:clamav&amp;amp;#32;0640&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/clamav/freshclam.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Проверить newsyslog -n -f /usr/local/etc/newsyslog.conf.d/virtual-mail.conf. Правила Exim относятся и к базовой установке без ClamAV; строки ClamAV добавлять только после его установки. Веб-журналы также включить в newsyslog: nginx требует сигнал USR1 для открытия новых файлов, PHP-FPM — штатный сигнал переоткрытия своих журналов. После пакетного обновления проверять владельцев, режимы и членство clamav в группах.&lt;br /&gt;
&lt;br /&gt;
== 13. Необязательно: Grafana Alloy ==&lt;br /&gt;
Alloy устанавливается отдельно при наличии центральных Loki/Prometheus и их CA. Не выставлять его API на публичный интерфейс. Стандартный набор: /var/log/auth.log, /var/log/messages, CPU/RAM, версии kernel/userland. Состояние и WAL разместить в /opt/alloy, конфигурацию — в /usr/local/etc/alloy.flow, запуск от отдельного пользователя alloy.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;groupshow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;groupadd&amp;amp;#32;alloy&lt;br /&gt;
pw&amp;amp;#32;usershow&amp;amp;#32;alloy&amp;amp;#32;&amp;gt;/dev/null&amp;amp;#32;2&amp;gt;&amp;amp;1&amp;amp;#32;||&amp;amp;#32;pw&amp;amp;#32;useradd&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-d&amp;amp;#32;/opt/alloy&amp;amp;#32;-s&amp;amp;#32;/usr/sbin/nologin&amp;amp;#32;-w&amp;amp;#32;no&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;alloy&amp;amp;#32;-g&amp;amp;#32;alloy&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_enable=NO&amp;amp;#32;alloy_user=alloy&amp;amp;#32;alloy_group=alloy&lt;br /&gt;
sysrc&amp;amp;#32;alloy_storage_path=/opt/alloy&amp;amp;#32;alloy_listen_address=127.0.0.1:9200&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Полная интеграция зависит от адресов приёмников и способа аутентификации организации. В конфигурации использовать password_file с отдельным root:alloy 0640 файлом, проверку CA и уникальную метку host. Для чтения auth/messages предоставить группе alloy доступ 0640 и сохранить те же владельца/группу в newsyslog; членство в широких системных группах не требуется. Проверить alloy validate, доставку контрольных событий, появление CPU/RAM и версий в центральной системе; только затем включить alloy_enable=YES. Этот раздел — параметры подключения, а не готовая конфигурация неизвестного центрального сервера.&lt;br /&gt;
&lt;br /&gt;
== 14. Обслуживание и диагностика ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;status&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
exim&amp;amp;#32;-bp&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/mainlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/exim/rejectlog&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/var/log/maillog&lt;br /&gt;
doveadm&amp;amp;#32;user&amp;amp;#32;alice@example.org&lt;br /&gt;
df&amp;amp;#32;-hT&amp;amp;#32;/&amp;amp;#32;/opt&lt;br /&gt;
openssl&amp;amp;#32;x509&amp;amp;#32;-in&amp;amp;#32;/usr/local/etc/mail-certs/fullchain.pem&amp;amp;#32;-noout&amp;amp;#32;-dates&amp;amp;#32;-subject&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Не публиковать users, приватные DKIM-ключи, TLS-ключ, секреты Alloy и содержимое пользовательских писем. Следить за пространством отдельно на / и /opt. Запланировать продление сертификата до срока истечения; после обновления проверить все его потребители, включая внешний HTTPS frontend. Перед заменой конфигураций сравнивать действующее состояние. Настройка Rspamd с автоматическим обучением при перемещениях INBOX ↔ Junk приведена в разделе 15; этот дополнительный этап выполняется после проверки базовой доставки.&lt;br /&gt;
&lt;br /&gt;
== 15. Rspamd: оценка почты и автоматическое обучение через IMAP ==&lt;br /&gt;
Этот дополнительный этап выполняется после работоспособной базовой установки Exim и Dovecot 2.4. Пакеты: Rspamd 4.1, Redis, dovecot24-pigeonhole и Python 3.12. Пользователи продолжают храниться в файлах; Redis используется для статистики Rspamd, а не как каталог почтовых ящиков. Данные и очередь обратной связи размещаются на UFS /opt.&lt;br /&gt;
&lt;br /&gt;
Схема рассчитана на общую Bayes-базу всех доменов. Любой пользователь, имеющий доступ к ящику, влияет на её обучение. Настройка autolearn = false отключает обучение по собственным оценкам Rspamd; обучение по явно выполненному пользователем IMAP-действию выполняет отдельная служба.&lt;br /&gt;
&lt;br /&gt;
* Exim очищает внешние X-Spam-заголовки, оценивает только неаутентифицированную SMTP-почту и добавляет локальную оценку.&lt;br /&gt;
* Действие Rspamd reject здесь является рекомендацией: по антиспаму нет SMTP-отказов, карантина или удаления. Недоступность Rspamd не блокирует доставку.&lt;br /&gt;
* Уже установленный ClamAV продолжает работать по прежней политике: обнаружение вируса отклоняет письмо, отказ сканера даёт временный SMTP-ответ.&lt;br /&gt;
* INBOX → Junk создаёт spam-событие; Junk → INBOX — ham-событие. IMAPSieve использует причину COPY как для IMAP MOVE, так и для прямого IMAP COPY между этими папками. APPEND, изменение флагов, Junk → Trash и другие папки не обучают фильтр.&lt;br /&gt;
* IMAP-процесс только пишет ограниченное событие на диск. Пароль контроллера и сетевой вызов rspamc находятся в отдельном root-процессе.&lt;br /&gt;
&lt;br /&gt;
=== 15.1. Установить пакеты и подготовить данные ===&lt;br /&gt;
Команды ниже относятся к новой установке выделенных Redis/Rspamd. Если Redis уже обслуживает другие приложения, не заменять его параметры: выделить отдельный экземпляр и согласованно изменить адрес подключения. Выполнять от root в /bin/sh. Проверить доступность /opt и план пакетного менеджера.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;$3&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;ufs&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;ok=1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!ok&amp;amp;#32;}&amp;amp;#x27;&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-n&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
pkg&amp;amp;#32;install&amp;amp;#32;-y&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=NO&amp;amp;#32;rspamd_enable=NO&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;onestop&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;onestop&lt;br /&gt;
pkg&amp;amp;#32;info&amp;amp;#32;rspamd&amp;amp;#32;redis&amp;amp;#32;dovecot24-pigeonhole&amp;amp;#32;python312&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/opt/mail/log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Если новые службы ещё не запущены, onestop может сообщить об отсутствии процесса; проверить sockstat. Следующее перемещение делается ровно один раз: целевые каталоги должны отсутствовать. Пакетные данные сохраняются на новом разделе, а штатные пути становятся ссылками. На уже настроенной системе этот блок не повторять.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/db/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/db/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;-d&amp;amp;#32;/var/log/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-L&amp;amp;#32;/var/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;&amp;amp;amp;&amp;amp;amp;&amp;amp;#32;test&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;||&amp;amp;#32;exit&amp;amp;#32;1&lt;br /&gt;
mv&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/opt/mail/rspamd&lt;br /&gt;
mv&amp;amp;#32;/var/log/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/var/db/rspamd&lt;br /&gt;
ln&amp;amp;#32;-s&amp;amp;#32;/opt/mail/log/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;-h&amp;amp;#32;root:wheel&amp;amp;#32;/var/db/rspamd&amp;amp;#32;/var/log/rspamd&lt;br /&gt;
chown&amp;amp;#32;rspamd:rspamd&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
chmod&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd&amp;amp;#32;/opt/mail/log/rspamd&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;rspamd&amp;amp;#32;-g&amp;amp;#32;rspamd&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd/tmp&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/redis&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;redis&amp;amp;#32;-g&amp;amp;#32;redis&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/log/redis&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В /etc/rc.conf.d/redis добавить required_dirs=&amp;quot;/opt/mail/redis /opt/mail/log/redis&amp;quot;, в /etc/rc.conf.d/rspamd — required_dirs=&amp;quot;/opt/mail/rspamd /opt/mail/log/rspamd&amp;quot;. Не стирать другие действующие параметры этих файлов.&lt;br /&gt;
&lt;br /&gt;
=== 15.2. Настроить Redis и Rspamd ===&lt;br /&gt;
Сохранить следующий одноразовый скрипт как /root/setup-rspamd.py, root:wheel 0600. Он предназначен для новой установки, отказывается заменять существующие файлы секретов и не выводит их содержимое. Redis и контроллер слушают только loopback. Локальным процессам также нужен пароль контроллера: secure_ip очищен. Вызов rspamadm для хеша использует сгенерированный пароль как кратковременный аргумент процесса; не запускать этот шаг в среде с недоверенными локальными shell-пользователями. Пароль не записывается в историю команд.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;grp,&amp;amp;#32;os,&amp;amp;#32;pwd,&amp;amp;#32;secrets,&amp;amp;#32;subprocess&lt;br /&gt;
assert&amp;amp;#32;os.geteuid()&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
os.umask(0o077)&lt;br /&gt;
conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/rspamd&amp;amp;#x27;)&lt;br /&gt;
local&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;local.d&amp;amp;#x27;&lt;br /&gt;
local.mkdir(exist_ok=True)&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write(path,&amp;amp;#32;value,&amp;amp;#32;group=&amp;amp;#x27;wheel&amp;amp;#x27;,&amp;amp;#32;mode=0o640):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path.write_text(value,&amp;amp;#32;encoding=&amp;amp;#x27;utf-8&amp;amp;#x27;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chown(path,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(group).gr_gid)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.chmod(path,&amp;amp;#32;mode)&lt;br /&gt;
&lt;br /&gt;
redis_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.password&amp;amp;#x27;&lt;br /&gt;
controller_secret&amp;amp;#32;=&amp;amp;#32;conf&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;controller.password&amp;amp;#x27;&lt;br /&gt;
assert&amp;amp;#32;not&amp;amp;#32;redis_secret.exists()&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;controller_secret.exists()&lt;br /&gt;
redis_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
controller_password&amp;amp;#32;=&amp;amp;#32;secrets.token_urlsafe(36)&lt;br /&gt;
write(redis_secret,&amp;amp;#32;redis_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
write(controller_secret,&amp;amp;#32;controller_password&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;\n&amp;amp;#x27;,&amp;amp;#32;mode=0o600)&lt;br /&gt;
hashed&amp;amp;#32;=&amp;amp;#32;subprocess.check_output(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;[&amp;amp;#x27;/usr/local/bin/rspamadm&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;pw&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-q&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;-p&amp;amp;#x27;,&amp;amp;#32;controller_password],&amp;amp;#32;text=True&lt;br /&gt;
).strip()&lt;br /&gt;
assert&amp;amp;#32;hashed.startswith(&amp;amp;#x27;$&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;Unexpected&amp;amp;#32;password&amp;amp;#32;hash&amp;amp;#32;format&amp;amp;#x27;&lt;br /&gt;
redis_conf&amp;amp;#32;=&amp;amp;#32;Path(&amp;amp;#x27;/usr/local/etc/redis.conf&amp;amp;#x27;)&lt;br /&gt;
with&amp;amp;#32;redis_conf.open(&amp;amp;#x27;a&amp;amp;#x27;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;#x27;\n#&amp;amp;#32;Dedicated&amp;amp;#32;Redis&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;Rspamd&amp;amp;#32;service.\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind&amp;amp;#32;127.0.0.1\nprotected-mode&amp;amp;#32;yes\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;dir&amp;amp;#32;/opt/mail/redis\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;logfile&amp;amp;#32;/opt/mail/log/redis/redis.log\nloglevel&amp;amp;#32;notice\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;appendonly&amp;amp;#32;yes\nappendfsync&amp;amp;#32;everysec\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;maxmemory&amp;amp;#32;256mb\nmaxmemory-policy&amp;amp;#32;noeviction\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;requirepass&amp;amp;#32;{redis_password}\n&amp;amp;#x27;)&lt;br /&gt;
os.chown(redis_conf,&amp;amp;#32;0,&amp;amp;#32;grp.getgrnam(&amp;amp;#x27;redis&amp;amp;#x27;).gr_gid)&lt;br /&gt;
os.chmod(redis_conf,&amp;amp;#32;0o640)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;redis.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;servers&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:6379&amp;amp;quot;;\npassword&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{redis_password}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-normal.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11333&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-controller.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;bind_socket&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;;\nsecure_ip&amp;amp;#32;=&amp;amp;#32;[];\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;f&amp;amp;#x27;password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\nenable_password&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;{hashed}&amp;amp;quot;;\n&amp;amp;#x27;,&amp;amp;#32;group=&amp;amp;#x27;rspamd&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;worker-proxy.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;count&amp;amp;#32;=&amp;amp;#32;0;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;options.inc&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;check_all_filters&amp;amp;#32;=&amp;amp;#32;true;\ntempdir&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd/tmp&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;logging.inc&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;level&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;info&amp;amp;quot;;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;classifier-bayes.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;autolearn&amp;amp;#32;=&amp;amp;#32;false;\nper_user&amp;amp;#32;=&amp;amp;#32;false;\nmin_learns&amp;amp;#32;=&amp;amp;#32;200;\n&amp;amp;#x27;)&lt;br /&gt;
for&amp;amp;#32;module&amp;amp;#32;in&amp;amp;#32;(&amp;amp;#x27;greylist&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;ratelimit&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;neural&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;force_actions&amp;amp;#x27;,&amp;amp;#32;&amp;amp;#x27;fuzzy_check&amp;amp;#x27;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write(local&amp;amp;#32;/&amp;amp;#32;(module&amp;amp;#32;+&amp;amp;#32;&amp;amp;#x27;.conf&amp;amp;#x27;),&amp;amp;#32;&amp;amp;#x27;enabled&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;)&lt;br /&gt;
write(local&amp;amp;#32;/&amp;amp;#32;&amp;amp;#x27;phishing.conf&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;\nopenphish_enabled&amp;amp;#32;=&amp;amp;#32;true;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_premium&amp;amp;#32;=&amp;amp;#32;false;\n&amp;amp;#x27;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#x27;openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;\n&amp;amp;#x27;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;mode=0o644)&lt;br /&gt;
print(&amp;amp;#x27;Configuration&amp;amp;#32;written;&amp;amp;#32;secrets&amp;amp;#32;are&amp;amp;#32;stored&amp;amp;#32;in&amp;amp;#32;root-only&amp;amp;#32;files.&amp;amp;#x27;)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chmod&amp;amp;#32;0600&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
python3.12&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
sysrc&amp;amp;#32;redis_enable=YES&amp;amp;#32;rspamd_enable=YES&lt;br /&gt;
service&amp;amp;#32;redis&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;start&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sockstat&amp;amp;#32;-46l&amp;amp;#32;|&amp;amp;#32;egrep&amp;amp;#32;&amp;amp;#x27;6379|11333|11334&amp;amp;#x27;&lt;br /&gt;
rm&amp;amp;#32;/root/setup-rspamd.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить отсутствие Redis/Rspamd на внешних адресах. В local.d/options.inc не задан чужой DNS-сервер: используется системный резолвер; для DNSBL нужен разрешённый их политикой резолвер. Пароли не печатать через cat и не включать дамп полного конфигурационного файла в общие логи. При ошибке не подключать Exim, пока configtest, запуск и авторизованный stat не проходят. Значение min_learns = 200 означает, что для работы Bayes-оценок требуются примеры обоих классов: по 200 spam и ham; остальные проверки могут работать до накопления этих примеров.&lt;br /&gt;
&lt;br /&gt;
==== 15.2.1. OpenPhish вместо устаревшего PhishTank ====&lt;br /&gt;
Бесплатный OpenPhish Community Feed подключён через штатный модуль phishing. API-ключ не требуется. Rspamd загружает и автоматически обновляет список по HTTPS, затем проверяет ссылки из писем по локальной копии. Источник &amp;lt;code&amp;gt;phishtank.rspamd.com&amp;lt;/code&amp;gt; отключён: эта DNS-зона больше не содержит данных, что отмечено в [https://raw.githubusercontent.com/rspamd/rspamd/4.2.3/ChangeLog журнале изменений Rspamd].&lt;br /&gt;
&lt;br /&gt;
Скрипт новой установки выше уже записывает эти параметры. На существующей установке создать или дополнить &amp;lt;code&amp;gt;/usr/local/etc/rspamd/local.d/phishing.conf&amp;lt;/code&amp;gt;, сохранив остальные действующие настройки:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
phishtank_enabled&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_enabled&amp;amp;#32;=&amp;amp;#32;true;&lt;br /&gt;
openphish_premium&amp;amp;#32;=&amp;amp;#32;false;&lt;br /&gt;
openphish_map&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;https://raw.githubusercontent.com/openphish/public_feed/refs/heads/main/feed.txt&amp;amp;quot;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверить конфигурацию и применить её без перезапуска Exim и Dovecot:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/rspamd/local.d/phishing.conf&lt;br /&gt;
rspamadm&amp;amp;#32;configtest&lt;br /&gt;
#&amp;amp;#32;Продолжить&amp;amp;#32;только&amp;amp;#32;после&amp;amp;#32;syntax&amp;amp;#32;OK:&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;reload&lt;br /&gt;
rspamadm&amp;amp;#32;configdump&amp;amp;#32;phishing&lt;br /&gt;
service&amp;amp;#32;rspamd&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В итоговой конфигурации должны быть &amp;lt;code&amp;gt;phishtank_enabled = false&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;openphish_enabled = true&amp;lt;/code&amp;gt; и &amp;lt;code&amp;gt;openphish_premium = false&amp;lt;/code&amp;gt;. Успешную загрузку списка подтверждает запись &amp;lt;code&amp;gt;parsed N elements from openphish feed&amp;lt;/code&amp;gt; в журнале Rspamd, где &amp;lt;code&amp;gt;N &amp;amp;gt; 0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Точное совпадение ссылки с базой даёт символ &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;; на проверенной установке его вес — 7 баллов. Порог маркировки — 6 баллов, при этом итоговая оценка учитывает все сработавшие правила. Режим Exim из раздела 15.3 сохраняется: по антиспаму письмо принимается и помечается, а при включённом разделе 15.7 доставляется в Junk («Спам»).&lt;br /&gt;
&lt;br /&gt;
Проверено 29 октября 2026 года: Rspamd загрузил и разобрал 300 ссылок. Письмо со ссылкой из базы получило &amp;lt;code&amp;gt;PHISHED_OPENPHISH&amp;lt;/code&amp;gt;, 7 баллов и действие &amp;lt;code&amp;gt;add header&amp;lt;/code&amp;gt;; отрицательный контроль этого символа не получил. Тесты выполнены через локальный &amp;lt;code&amp;gt;/checkv2&amp;lt;/code&amp;gt; без SMTP/IMAP-доставки и без обучения: счётчик &amp;lt;code&amp;gt;learned&amp;lt;/code&amp;gt; остался 3 → 3. Число 300 относится к этой проверке; состав базы меняется. Конфигурации Exim и доставки Dovecot, пороги и настройка Bayes не изменялись.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только OpenPhish, установить &amp;lt;code&amp;gt;openphish_enabled = false;&amp;lt;/code&amp;gt;, выполнить &amp;lt;code&amp;gt;rspamadm configtest&amp;lt;/code&amp;gt; и после успешной проверки — &amp;lt;code&amp;gt;service rspamd reload&amp;lt;/code&amp;gt;. PhishTank оставить выключенным.&lt;br /&gt;
&lt;br /&gt;
Источник настройки: [https://docs.rspamd.com/modules/phishing/ Rspamd: модуль phishing и OpenPhish].&lt;br /&gt;
&lt;br /&gt;
=== 15.3. Подключить Exim без антиспам-отказов ===&lt;br /&gt;
В главной части /usr/local/etc/exim/configure, до begin acl, добавить одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
spamd_address&amp;amp;#32;=&amp;amp;#32;127.0.0.1&amp;amp;#32;11333&amp;amp;#32;variant=rspamd&amp;amp;#32;tmo=20s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В acl_check_data после всех существующих проверок отправителя и после ClamAV, если он установлен, но перед последним accept, добавить следующий блок. Не заменять существующий malware-блок и не добавлять deny на основе $spam_action.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Rspamd:&amp;amp;#32;annotate&amp;amp;#32;unauthenticated&amp;amp;#32;incoming&amp;amp;#32;mail;&amp;amp;#32;keep&amp;amp;#32;antivirus&amp;amp;#32;policy.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;remove_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score&amp;amp;#32;:&amp;amp;#32;X-Spam-Flag&amp;amp;#32;:&amp;amp;#32;X-Spam-Status&amp;amp;#32;:&amp;amp;#32;X-Spam-Report&amp;amp;#32;:&amp;amp;#32;X-Rspamd-Action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;warn&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;!authenticated&amp;amp;#32;=&amp;amp;#32;*&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;spam&amp;amp;#32;=&amp;amp;#32;nobody:true/defer_ok&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Score:&amp;amp;#32;$spam_score&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Rspamd-Action:&amp;amp;#32;$spam_action&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;add_header&amp;amp;#32;=&amp;amp;#32;X-Spam-Flag:&amp;amp;#32;${if&amp;amp;#32;inlist{$spam_action}{add&amp;amp;#32;header:rewrite&amp;amp;#32;subject:reject:quarantine:discard}{YES}{NO}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
exim&amp;amp;#32;-bV&lt;br /&gt;
service&amp;amp;#32;exim&amp;amp;#32;reload&lt;br /&gt;
exim&amp;amp;#32;-bP&amp;amp;#32;spamd_address&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Проверка true возвращает результат оценки независимо от порога, а defer_ok позволяет продолжить SMTP при недоступном Rspamd. Очистка заголовков применяется ко всем SMTP-сообщениям, оценка — только к неаутентифицированным. Отправка через AUTH 465/587 не сканируется Rspamd и не может оставить собственную поддельную метку. Защита ClamAV действует независимо. Имена X-Spam-заголовков должны совпадать с необязательным правилом доставки ниже.&lt;br /&gt;
&lt;br /&gt;
=== 15.4. Создать защищённую очередь обратной связи ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0750&amp;amp;#32;/opt/mail/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;vmail&amp;amp;#32;-g&amp;amp;#32;vmail&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/pending&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;/opt/mail/rspamd-feedback/processed&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0755&amp;amp;#32;/usr/local/etc/dovecot/rspamd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ниже приведены три обязательных файла: сборщик, административная библиотека/CLI и фоновый обработчик. Worker импортирует административный модуль, тот импортирует сборщик: пропускать feedback-admin.py нельзя, даже если ручное обучение не используется. Все три файла принадлежат root:wheel, режим 0755, каталог не доступен на запись vmail. Сборщик принимает только фиксированные переходы INBOX/Junk, сообщение не более 50 MiB и ограниченную локальную очередь; к Rspamd он не обращается.&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback.py ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;IMAPSieve&amp;amp;#32;collector:&amp;amp;#32;bounded&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;contacts&amp;amp;#32;Rspamd.&lt;br /&gt;
&lt;br /&gt;
Usage:&amp;amp;#32;feedback.py&amp;amp;#32;[--queue&amp;amp;#32;DIR]&amp;amp;#32;spam|ham&amp;amp;#32;USER&amp;amp;#32;SOURCE&amp;amp;#32;DESTINATION&lt;br /&gt;
The&amp;amp;#32;queue&amp;amp;#32;must&amp;amp;#32;already&amp;amp;#32;exist&amp;amp;#32;and&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;the&amp;amp;#32;IMAP&amp;amp;#32;mail&amp;amp;#32;UID,&amp;amp;#32;mode&amp;amp;#32;0700.&lt;br /&gt;
Programs/configuration&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;that&amp;amp;#32;UID.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;secrets&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime,&amp;amp;#32;timezone&lt;br /&gt;
&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;50&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
DEFAULT_QUEUE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;quot;&lt;br /&gt;
MAX_PENDING_BYTES&amp;amp;#32;=&amp;amp;#32;512&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
MAX_PENDING_EVENTS&amp;amp;#32;=&amp;amp;#32;1024&lt;br /&gt;
MIN_FREE_BYTES&amp;amp;#32;=&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;1024&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;FeedbackError(Exception):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;utc_now():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;datetime.now(timezone.utc).isoformat(timespec=&amp;amp;quot;seconds&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;fsync_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;expected&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;spam&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;),&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;:&amp;amp;#32;(&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;expected&amp;amp;#32;or&amp;amp;#32;(source,&amp;amp;#32;destination)&amp;amp;#32;!=&amp;amp;#32;expected[label]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;is&amp;amp;#32;outside&amp;amp;#32;the&amp;amp;#32;configured&amp;amp;#32;INBOX/Junk&amp;amp;#32;transitions&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;user&amp;amp;#32;or&amp;amp;#32;len(user)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;320&amp;amp;#32;or&amp;amp;#32;any(ord(c)&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;32&amp;amp;#32;or&amp;amp;#32;ord(c)&amp;amp;#32;==&amp;amp;#32;127&amp;amp;#32;for&amp;amp;#32;c&amp;amp;#32;in&amp;amp;#32;user):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;IMAP&amp;amp;#32;user&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;At&amp;amp;#32;most&amp;amp;#32;two&amp;amp;#32;seconds&amp;amp;#32;waiting;&amp;amp;#32;IMAP&amp;amp;#32;must&amp;amp;#32;not&amp;amp;#32;wait&amp;amp;#32;indefinitely&amp;amp;#32;on&amp;amp;#32;a&amp;amp;#32;lock.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(queue&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;until&amp;amp;#32;=&amp;amp;#32;time.monotonic()&amp;amp;#32;+&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;BlockingIOError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;time.monotonic()&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;until:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;lock&amp;amp;#32;is&amp;amp;#32;busy&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;time.sleep(0.02)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events=MAX_PENDING_EVENTS):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(queue)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;entry.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;.collector.lock&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;event&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;entry.is_dir(follow_symlinks=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;item&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;queue&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_DIRECTORY&amp;amp;quot;,&amp;amp;#32;0)&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;os.open(entry.path,&amp;amp;#32;flags)&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(os.stat(name,&amp;amp;#32;dir_fd=directory,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;(Path(entry.path)&amp;amp;#32;/&amp;amp;#32;name).lstat())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unexpected&amp;amp;#32;file&amp;amp;#32;type&amp;amp;#32;in&amp;amp;#32;pending&amp;amp;#32;event&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;directory&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;total&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;collect(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;*,&amp;amp;#32;max_bytes=MAX_MESSAGE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_pending_bytes=MAX_PENDING_BYTES,&amp;amp;#32;max_events=MAX_PENDING_EVENTS,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;min_free_bytes=MIN_FREE_BYTES):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;validate_event(label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;queue&amp;amp;#32;=&amp;amp;#32;Path(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;queue.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;os.geteuid()&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;must&amp;amp;#32;belong&amp;amp;#32;to&amp;amp;#32;this&amp;amp;#32;UID&amp;amp;#32;and&amp;amp;#32;have&amp;amp;#32;mode&amp;amp;#32;0700&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;collection_lock(queue):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;current_bytes&amp;amp;#32;=&amp;amp;#32;pending_usage(queue,&amp;amp;#32;max_events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Reserve&amp;amp;#32;metadata&amp;amp;#32;and&amp;amp;#32;preserve&amp;amp;#32;1&amp;amp;#32;GiB&amp;amp;#32;even&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;maximum-sized&amp;amp;#32;message.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;current_bytes&amp;amp;#32;+&amp;amp;#32;16384&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;max_pending_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(queue).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;min_free_bytes&amp;amp;#32;+&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;16384:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_bytes,&amp;amp;#32;min(max_bytes,&amp;amp;#32;max_pending_bytes&amp;amp;#32;-&amp;amp;#32;current_bytes&amp;amp;#32;-&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_write_event(stream,&amp;amp;#32;queue,&amp;amp;#32;label,&amp;amp;#32;user,&amp;amp;#32;source,&amp;amp;#32;destination,&amp;amp;#32;max_bytes,&amp;amp;#32;available_bytes):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;secrets.token_hex(16)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;(&amp;amp;quot;.tmp-&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;final&amp;amp;#32;=&amp;amp;#32;queue&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;hashlib.sha256()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;True:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;block&amp;amp;#32;=&amp;amp;#32;stream.read(min(65536,&amp;amp;#32;max_bytes&amp;amp;#32;+&amp;amp;#32;1&amp;amp;#32;-&amp;amp;#32;total))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;block:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;len(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;max_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;message&amp;amp;#32;exceeds&amp;amp;#32;the&amp;amp;#32;50&amp;amp;#32;MiB&amp;amp;#32;collection&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;available_bytes:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;byte&amp;amp;#32;limit&amp;amp;#32;reached&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest.update(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(block)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;==&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;empty&amp;amp;#32;message&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;:&amp;amp;#32;utc_now(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;:&amp;amp;#32;user,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;:&amp;amp;#32;source,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;:&amp;amp;#32;destination,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;:&amp;amp;#32;total,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;digest.hexdigest(),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(temporary&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;w&amp;amp;quot;,&amp;amp;#32;encoding=&amp;amp;quot;utf-8&amp;amp;quot;,&amp;amp;#32;newline=&amp;amp;quot;\n&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;json.dump(metadata,&amp;amp;#32;output,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(&amp;amp;quot;\n&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(temporary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(temporary,&amp;amp;#32;final)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fsync_directory(queue)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;Exception:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Remove&amp;amp;#32;only&amp;amp;#32;our&amp;amp;#32;two&amp;amp;#32;known&amp;amp;#32;partial&amp;amp;#32;files,&amp;amp;#32;never&amp;amp;#32;recurse&amp;amp;#32;into&amp;amp;#32;a&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(temporary&amp;amp;#32;/&amp;amp;#32;name).unlink()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary.rmdir()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_QUEUE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;user&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;destination&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collect(sys.stdin.buffer,&amp;amp;#32;args.queue,&amp;amp;#32;args.label,&amp;amp;#32;args.user,&amp;amp;#32;args.source,&amp;amp;#32;args.destination)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError)&amp;amp;#32;as&amp;amp;#32;exc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;message&amp;amp;#32;data,&amp;amp;#32;addresses,&amp;amp;#32;paths&amp;amp;#32;supplied&amp;amp;#32;by&amp;amp;#32;users,&amp;amp;#32;or&amp;amp;#32;tracebacks&amp;amp;#32;in&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;detail&amp;amp;#32;=&amp;amp;#32;str(exc)&amp;amp;#32;if&amp;amp;#32;isinstance(exc,&amp;amp;#32;FeedbackError)&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;local&amp;amp;#32;IO&amp;amp;#32;error&amp;amp;#32;errno=&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;str(exc.errno)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;rspamd-feedback:&amp;amp;#32;collection&amp;amp;#32;failed:&amp;amp;#32;&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;detail&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;;&amp;amp;#32;no&amp;amp;#32;training&amp;amp;#32;attempted&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;syslog&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.openlog(&amp;amp;quot;rspamd-feedback&amp;amp;quot;,&amp;amp;#32;syslog.LOG_PID,&amp;amp;#32;syslog.LOG_MAIL)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;syslog.syslog(syslog.LOG_ERR,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;ImportError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(message,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-admin.py ====&lt;br /&gt;
Это общая проверка событий, защищённые снимки, журнал результатов и диагностические команды. Ручная learn-команда сохранена для исключительных случаев; обычные IMAP-события обрабатывает worker из следующего файла.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;review&amp;amp;#32;and&amp;amp;#32;explicit&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback.&lt;br /&gt;
&lt;br /&gt;
No&amp;amp;#32;command&amp;amp;#32;trains&amp;amp;#32;automatically.&amp;amp;#32;list/show&amp;amp;#32;are&amp;amp;#32;local&amp;amp;#32;reads.&amp;amp;#32;learn&amp;amp;#32;ID&amp;amp;#32;spam|ham&lt;br /&gt;
is&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;command&amp;amp;#32;that&amp;amp;#32;invokes&amp;amp;#32;rspamc.&amp;amp;#32;reject&amp;amp;#32;ID&amp;amp;#32;archives&amp;amp;#32;without&amp;amp;#32;training.&lt;br /&gt;
Queue&amp;amp;#32;root&amp;amp;#32;and&amp;amp;#32;processed&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;non-writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users;&lt;br /&gt;
pending&amp;amp;#32;is&amp;amp;#32;separately&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;vmail,&amp;amp;#32;mode&amp;amp;#32;0700.&amp;amp;#32;Archived&amp;amp;#32;originals&amp;amp;#32;remain&amp;amp;#32;in&lt;br /&gt;
processed/ID/source;&amp;amp;#32;only&amp;amp;#32;root-owned&amp;amp;#32;snapshots&amp;amp;#32;are&amp;amp;#32;used&amp;amp;#32;for&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;review.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;contextlib&amp;amp;#32;import&amp;amp;#32;contextmanager&lt;br /&gt;
from&amp;amp;#32;email&amp;amp;#32;import&amp;amp;#32;policy&lt;br /&gt;
from&amp;amp;#32;email.parser&amp;amp;#32;import&amp;amp;#32;BytesHeaderParser&lt;br /&gt;
import&amp;amp;#32;hashlib&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;shutil&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
&lt;br /&gt;
spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_collector&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback.py&amp;amp;quot;))&lt;br /&gt;
collector&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(spec)&lt;br /&gt;
spec.loader.exec_module(collector)&lt;br /&gt;
MAX_MESSAGE&amp;amp;#32;=&amp;amp;#32;collector.MAX_MESSAGE&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;collector.FeedbackError&lt;br /&gt;
ID_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{32}\Z&amp;amp;quot;)&lt;br /&gt;
DEFAULT_ROOT&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/opt/mail/rspamd-feedback&amp;amp;quot;&lt;br /&gt;
DEFAULT_PASSWORD_FILE&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;/usr/local/etc/rspamd/controller.password&amp;amp;quot;&lt;br /&gt;
HEADERS&amp;amp;#32;=&amp;amp;#32;(&amp;amp;quot;From&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;To&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Date&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Subject&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Message-ID&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Status&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Spam-Score&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;X-Rspamd-Action&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;emit(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;require_root():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;hasattr(os,&amp;amp;#32;&amp;amp;quot;geteuid&amp;amp;quot;)&amp;amp;#32;or&amp;amp;#32;os.geteuid()&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;administrative&amp;amp;#32;command&amp;amp;#32;requires&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_id(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;ID&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;exactly&amp;amp;#32;32&amp;amp;#32;lowercase&amp;amp;#32;hexadecimal&amp;amp;#32;characters&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;event_id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class&amp;amp;#32;Directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Nofollow&amp;amp;#32;directory&amp;amp;#32;descriptor&amp;amp;#32;on&amp;amp;#32;FreeBSD;&amp;amp;#32;portable&amp;amp;#32;fallback&amp;amp;#32;for&amp;amp;#32;local&amp;amp;#32;tests.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__init__(self,&amp;amp;#32;path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.path&amp;amp;#32;=&amp;amp;#32;Path(path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;self.path.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;expected&amp;amp;#32;a&amp;amp;#32;real&amp;amp;#32;directory&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;self.fd&amp;amp;#32;=&amp;amp;#32;os.open(self.path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_DIRECTORY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__enter__(self):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;self&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;__exit__(self,&amp;amp;#32;*args):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(self.fd)&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;def&amp;amp;#32;read(self,&amp;amp;#32;name,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;&amp;amp;quot;/&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;&amp;amp;quot;\\&amp;amp;quot;&amp;amp;#32;in&amp;amp;#32;name&amp;amp;#32;or&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;.&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;..&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;internal&amp;amp;#32;filename&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;self.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(name,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;os.O_NOFOLLOW&amp;amp;#32;|&amp;amp;#32;os.O_NONBLOCK,&amp;amp;#32;dir_fd=self.fd)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;self.path&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;symlink&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;an&amp;amp;#32;event&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;os.O_RDONLY&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;rb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;source:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.fstat(source.fileno())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;info.st_nlink&amp;amp;#32;!=&amp;amp;#32;1&amp;amp;#32;or&amp;amp;#32;info.st_size&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;a&amp;amp;#32;bounded,&amp;amp;#32;single-link&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;source.read(limit&amp;amp;#32;+&amp;amp;#32;1)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(result)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;file&amp;amp;#32;exceeded&amp;amp;#32;the&amp;amp;#32;size&amp;amp;#32;limit&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;result&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;read_event(directory,&amp;amp;#32;event_id):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;On&amp;amp;#32;POSIX&amp;amp;#32;open&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;relative&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;already&amp;amp;#32;opened&amp;amp;#32;parent&amp;amp;#32;descriptor.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;parent&amp;amp;#32;path&amp;amp;#32;is&amp;amp;#32;deployment-owned,&amp;amp;#32;and&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;event&amp;amp;#32;is&amp;amp;#32;rejected.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(Path(directory)&amp;amp;#32;/&amp;amp;#32;event_id)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;entry.read(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;MAX_MESSAGE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;user&amp;amp;quot;),&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;event_id&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;bytes&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;len(message)&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;hashlib.sha256(message).hexdigest():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;event&amp;amp;#32;content&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;match&amp;amp;#32;its&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;metadata,&amp;amp;#32;message&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;write_private(path,&amp;amp;#32;value,&amp;amp;#32;*,&amp;amp;#32;json_value=False):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;flags&amp;amp;#32;=&amp;amp;#32;os.O_WRONLY&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;os.O_EXCL&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_BINARY&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(path,&amp;amp;#32;flags,&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.fdopen(fd,&amp;amp;#32;&amp;amp;quot;wb&amp;amp;quot;)&amp;amp;#32;as&amp;amp;#32;output:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;json_value:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;value&amp;amp;#32;=&amp;amp;#32;(json.dumps(value,&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True)&amp;amp;#32;+&amp;amp;#32;&amp;amp;quot;\n&amp;amp;quot;).encode()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.write(value)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;output.flush()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.fsync(output.fileno())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;update_receipt(directory,&amp;amp;#32;receipt):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;temporary&amp;amp;#32;=&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin-new.json&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(temporary,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.replace(temporary,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(directory)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;check_root_directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;Path(path).stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;administrative&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;group/other&amp;amp;#32;writable&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.admin.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(fd,&amp;amp;#32;fcntl.LOCK_EX)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;yield&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(fd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;receipt_for(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory)&amp;amp;#32;as&amp;amp;#32;entry:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;json.loads(entry.read(&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;find_prior(root,&amp;amp;#32;sha256):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;damaged&amp;amp;#32;journal&amp;amp;#32;must&amp;amp;#32;never&amp;amp;#32;silently&amp;amp;#32;allow&amp;amp;#32;repeated&amp;amp;#32;training.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;is&amp;amp;#32;incomplete;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;before&amp;amp;#32;more&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&amp;amp;#32;==&amp;amp;#32;sha256&amp;amp;#32;and&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;None&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label=None,&amp;amp;#32;reason=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;shutil.disk_usage(root).free&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;collector.MIN_FREE_BYTES&amp;amp;#32;+&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;insufficient&amp;amp;#32;free&amp;amp;#32;disk&amp;amp;#32;space&amp;amp;#32;to&amp;amp;#32;preserve&amp;amp;#32;the&amp;amp;#32;reviewed&amp;amp;#32;snapshot&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive.mkdir(mode=0o700)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;schema&amp;amp;quot;:&amp;amp;#32;1,&amp;amp;#32;&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;sha256&amp;amp;quot;:&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;],&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;reviewed_utc&amp;amp;quot;:&amp;amp;#32;collector.utc_now(),&amp;amp;#32;&amp;amp;quot;status&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;preparing&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;selected_class&amp;amp;quot;:&amp;amp;#32;label,&amp;amp;#32;&amp;amp;quot;reason&amp;amp;quot;:&amp;amp;#32;reason}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;,&amp;amp;#32;receipt,&amp;amp;#32;json_value=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;message)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;write_private(archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;json.dumps(metadata,&amp;amp;#32;ensure_ascii=True).encode())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Keep&amp;amp;#32;the&amp;amp;#32;complete&amp;amp;#32;original&amp;amp;#32;event.&amp;amp;#32;Never&amp;amp;#32;traverse&amp;amp;#32;or&amp;amp;#32;execute&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;source/.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.rename(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id,&amp;amp;#32;archive&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;status&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;collector.fsync_directory(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;archive,&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;executable.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;path&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[str(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;an&amp;amp;#32;absolute&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned,&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;rspamc&amp;amp;#32;reads&amp;amp;#32;the&amp;amp;#32;file&amp;amp;#32;itself;&amp;amp;#32;the&amp;amp;#32;secret&amp;amp;#32;never&amp;amp;#32;appears&amp;amp;#32;in&amp;amp;#32;argv&amp;amp;#32;or&amp;amp;#32;logs.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;learn_&amp;amp;quot;&amp;amp;#32;+&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;No&amp;amp;#32;-u/-d:&amp;amp;#32;one&amp;amp;#32;shared&amp;amp;#32;Bayes&amp;amp;#32;classifier.&amp;amp;#32;No&amp;amp;#32;shell&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;input=message,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;timeout=timeout,&amp;amp;#32;check=False).returncode&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;learn_event(root,&amp;amp;#32;event_id,&amp;amp;#32;label,&amp;amp;#32;*,&amp;amp;#32;executable,&amp;amp;#32;controller,&amp;amp;#32;password_file=None,&amp;amp;#32;timeout=30):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;training&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;prior&amp;amp;#32;=&amp;amp;#32;find_prior(root,&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prior:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;this&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;prior&amp;amp;#32;learned&amp;amp;#32;or&amp;amp;#32;uncertain&amp;amp;#32;event;&amp;amp;#32;inspect&amp;amp;#32;it&amp;amp;#32;and&amp;amp;#32;reject&amp;amp;#32;the&amp;amp;#32;duplicate&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rc&amp;amp;#32;=&amp;amp;#32;run_learning(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;subprocess.TimeoutExpired:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;timeout;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;do&amp;amp;#32;not&amp;amp;#32;automatically&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_failed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;could&amp;amp;#32;not&amp;amp;#32;start&amp;amp;#32;or&amp;amp;#32;local&amp;amp;#32;configuration&amp;amp;#32;was&amp;amp;#32;invalid&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;rc&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;rc&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;rc:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;inspect&amp;amp;#32;server&amp;amp;#32;before&amp;amp;#32;any&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;reject_event(root,&amp;amp;#32;event_id,&amp;amp;#32;reason=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_id(event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;len(reason)&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;500:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;reason&amp;amp;#32;is&amp;amp;#32;too&amp;amp;#32;long&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;&amp;amp;quot;rejected&amp;amp;quot;,&amp;amp;#32;reason=reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;receipt&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;list_events(root,&amp;amp;#32;state,&amp;amp;#32;limit):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;((&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;)&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;(state,)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;directory.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(directory):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;directory.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;limit:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;notice&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;limit&amp;amp;#32;reached&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;limit&amp;amp;quot;:&amp;amp;#32;limit})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;Directory(entry)&amp;amp;#32;as&amp;amp;#32;item:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(item.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;metadata.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;id&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;destination&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;bytes&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;state&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;review&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;receipt_for(entry)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(row)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;state&amp;amp;quot;:&amp;amp;#32;name,&amp;amp;#32;&amp;amp;quot;error&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;#32;event;&amp;amp;#32;manual&amp;amp;#32;inspection&amp;amp;#32;required&amp;amp;quot;})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;show_event(root,&amp;amp;#32;event_id,&amp;amp;#32;state,&amp;amp;#32;path_only):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;read_event(root&amp;amp;#32;/&amp;amp;#32;state,&amp;amp;#32;check_id(event_id))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;path_only:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;event_id,&amp;amp;#32;&amp;amp;quot;message_path&amp;amp;quot;:&amp;amp;#32;str(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;message.eml&amp;amp;quot;)})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;headers&amp;amp;#32;=&amp;amp;#32;BytesHeaderParser(policy=policy.default).parsebytes(message[:min(len(message),&amp;amp;#32;262144)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;metadata&amp;amp;quot;:&amp;amp;#32;metadata,&amp;amp;#32;&amp;amp;quot;headers&amp;amp;quot;:&amp;amp;#32;{name:&amp;amp;#32;[str(v)[:2000]&amp;amp;#32;for&amp;amp;#32;v&amp;amp;#32;in&amp;amp;#32;headers.get_all(name,&amp;amp;#32;[])[:10]]&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;HEADERS}})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit({&amp;amp;quot;review&amp;amp;quot;:&amp;amp;#32;receipt_for(root&amp;amp;#32;/&amp;amp;#32;state&amp;amp;#32;/&amp;amp;#32;event_id)})&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;warn_processed_storage(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;event&amp;amp;#32;in&amp;amp;#32;processed.iterdir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;ID_RE.fullmatch(event.name)&amp;amp;#32;or&amp;amp;#32;event.is_symlink()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;event.is_dir():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;prefix&amp;amp;#32;in&amp;amp;#32;(event,&amp;amp;#32;event&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;prefix.is_symlink():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(prefix&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;total&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pass&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;total&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_BYTES&amp;amp;#32;or&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;WARNING&amp;amp;#32;processed&amp;amp;#32;archive&amp;amp;#32;reached&amp;amp;#32;512&amp;amp;#32;MiB&amp;amp;#32;or&amp;amp;#32;1024&amp;amp;#32;events;&amp;amp;#32;review&amp;amp;#32;retention&amp;amp;#32;manually;&amp;amp;#32;nothing&amp;amp;#32;deleted&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sub&amp;amp;#32;=&amp;amp;#32;parser.add_subparsers(dest=&amp;amp;quot;command&amp;amp;quot;,&amp;amp;#32;required=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;list&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;JSON&amp;amp;#32;lines,&amp;amp;#32;metadata&amp;amp;#32;only&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;all&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;listing.add_argument(&amp;amp;quot;--limit&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=100)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;show&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;selected&amp;amp;#32;headers&amp;amp;#32;only;&amp;amp;#32;never&amp;amp;#32;dumps&amp;amp;#32;the&amp;amp;#32;body&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--state&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;),&amp;amp;#32;default=&amp;amp;quot;pending&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;showing.add_argument(&amp;amp;quot;--path&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;learn&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;explicitly&amp;amp;#32;train&amp;amp;#32;one&amp;amp;#32;reviewed&amp;amp;#32;event,&amp;amp;#32;then&amp;amp;#32;archive&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;choices=(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=DEFAULT_PASSWORD_FILE,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;help=&amp;amp;quot;root-only&amp;amp;#32;plaintext&amp;amp;#32;secret&amp;amp;#32;file;&amp;amp;#32;use&amp;amp;#32;--no-password&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;an&amp;amp;#32;intentionally&amp;amp;#32;trusted&amp;amp;#32;local&amp;amp;#32;controller&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--no-password&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learning.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting&amp;amp;#32;=&amp;amp;#32;sub.add_parser(&amp;amp;quot;reject&amp;amp;quot;,&amp;amp;#32;help=&amp;amp;quot;archive&amp;amp;#32;one&amp;amp;#32;event&amp;amp;#32;without&amp;amp;#32;training&amp;amp;#32;or&amp;amp;#32;deleting&amp;amp;#32;it&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;id&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rejecting.add_argument(&amp;amp;quot;--reason&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;review&amp;amp;#32;rejected&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;queue&amp;amp;#32;root&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn_processed_storage(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;list&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.limit&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;10000:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;limit&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;1&amp;amp;#32;and&amp;amp;#32;10000&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;list_events(root,&amp;amp;#32;args.state,&amp;amp;#32;args.limit)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;show&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;show_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.state,&amp;amp;#32;args.path)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;args.command&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;reject&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(reject_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.reason))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;timeout&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;between&amp;amp;#32;0&amp;amp;#32;and&amp;amp;#32;300&amp;amp;#32;seconds&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;result&amp;amp;#32;=&amp;amp;#32;learn_event(root,&amp;amp;#32;args.id,&amp;amp;#32;args.label,&amp;amp;#32;executable=args.rspamc_bin,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;controller=args.controller,&amp;amp;#32;password_file=None&amp;amp;#32;if&amp;amp;#32;args.no_password&amp;amp;#32;else&amp;amp;#32;args.password_file,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timeout=args.timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;emit(result)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&amp;amp;#32;if&amp;amp;#32;result[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;else&amp;amp;#32;2&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(FeedbackError,&amp;amp;#32;OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(&amp;amp;quot;rspamd-feedback-admin:&amp;amp;#32;operation&amp;amp;#32;refused&amp;amp;#32;or&amp;amp;#32;failed;&amp;amp;#32;inspect&amp;amp;#32;configuration/event&amp;amp;#32;locally&amp;amp;quot;,&amp;amp;#32;file=sys.stderr)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== /usr/local/libexec/rspamd-feedback/feedback-worker.py ====&lt;br /&gt;
Служба обрабатывает события по точному времени, сохраняет намерение до запроса обучения и исключает повтор одного и того же класса для того же содержимого. Обратный переход передаёт противоположный класс. Недоступный до запроса контроллер оставляет событие pending. Неопределённый результат уже отправленного запроса автоматически не повторяется.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/local/bin/python3.12&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Root-only&amp;amp;#32;background&amp;amp;#32;learning&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;bounded&amp;amp;#32;IMAPSieve&amp;amp;#32;feedback&amp;amp;#32;queue.&lt;br /&gt;
&lt;br /&gt;
The&amp;amp;#32;collector&amp;amp;#32;remains&amp;amp;#32;a&amp;amp;#32;local&amp;amp;#32;spool&amp;amp;#32;writer.&amp;amp;#32;This&amp;amp;#32;process&amp;amp;#32;owns&amp;amp;#32;controller&lt;br /&gt;
credentials,&amp;amp;#32;learns&amp;amp;#32;only&amp;amp;#32;validated&amp;amp;#32;INBOX/Junk&amp;amp;#32;events,&amp;amp;#32;preserves&amp;amp;#32;every&amp;amp;#32;original,&lt;br /&gt;
and&amp;amp;#32;never&amp;amp;#32;retries&amp;amp;#32;a&amp;amp;#32;training&amp;amp;#32;operation&amp;amp;#32;with&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result.&amp;amp;#32;Run&amp;amp;#32;foreground&lt;br /&gt;
under&amp;amp;#32;FreeBSD&amp;amp;#32;daemon(8),&amp;amp;#32;or&amp;amp;#32;use&amp;amp;#32;--once&amp;amp;#32;for&amp;amp;#32;one&amp;amp;#32;bounded&amp;amp;#32;batch.&lt;br /&gt;
&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
from&amp;amp;#32;__future__&amp;amp;#32;import&amp;amp;#32;annotations&lt;br /&gt;
&lt;br /&gt;
import&amp;amp;#32;argparse&lt;br /&gt;
from&amp;amp;#32;datetime&amp;amp;#32;import&amp;amp;#32;datetime&lt;br /&gt;
import&amp;amp;#32;importlib.util&lt;br /&gt;
import&amp;amp;#32;json&lt;br /&gt;
import&amp;amp;#32;os&lt;br /&gt;
from&amp;amp;#32;pathlib&amp;amp;#32;import&amp;amp;#32;Path&lt;br /&gt;
import&amp;amp;#32;re&lt;br /&gt;
import&amp;amp;#32;signal&lt;br /&gt;
import&amp;amp;#32;stat&lt;br /&gt;
import&amp;amp;#32;subprocess&lt;br /&gt;
import&amp;amp;#32;sys&lt;br /&gt;
import&amp;amp;#32;threading&lt;br /&gt;
import&amp;amp;#32;time&lt;br /&gt;
&lt;br /&gt;
_spec&amp;amp;#32;=&amp;amp;#32;importlib.util.spec_from_file_location(&amp;amp;quot;feedback_worker_admin&amp;amp;quot;,&amp;amp;#32;Path(__file__).with_name(&amp;amp;quot;feedback-admin.py&amp;amp;quot;))&lt;br /&gt;
admin&amp;amp;#32;=&amp;amp;#32;importlib.util.module_from_spec(_spec)&lt;br /&gt;
_spec.loader.exec_module(admin)&lt;br /&gt;
FeedbackError&amp;amp;#32;=&amp;amp;#32;admin.FeedbackError&lt;br /&gt;
SHA_RE&amp;amp;#32;=&amp;amp;#32;re.compile(r&amp;amp;quot;[0-9a-f]{64}\Z&amp;amp;quot;)&lt;br /&gt;
MAX_JOURNAL_EVENTS&amp;amp;#32;=&amp;amp;#32;20000&lt;br /&gt;
UNCERTAIN&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;preparing&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;log_event(kind,&amp;amp;#32;**fields):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Callers&amp;amp;#32;pass&amp;amp;#32;only&amp;amp;#32;fixed&amp;amp;#32;diagnostics,&amp;amp;#32;numeric&amp;amp;#32;counts,&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;classes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;print(json.dumps({&amp;amp;quot;event&amp;amp;quot;:&amp;amp;#32;kind,&amp;amp;#32;**fields},&amp;amp;#32;ensure_ascii=True,&amp;amp;#32;sort_keys=True),&amp;amp;#32;flush=True)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_timestamp_ns(value):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(value,&amp;amp;#32;str):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;missing&amp;amp;#32;timestamp&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parsed&amp;amp;#32;=&amp;amp;#32;datetime.fromisoformat(value.replace(&amp;amp;quot;Z&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;+00:00&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;parsed.tzinfo&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;timestamp&amp;amp;#32;must&amp;amp;#32;have&amp;amp;#32;timezone&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;int(parsed.timestamp()&amp;amp;#32;*&amp;amp;#32;1_000_000_000)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;order_events(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;bounded&amp;amp;#32;metadata,&amp;amp;#32;ordered&amp;amp;#32;by&amp;amp;#32;collector&amp;amp;#32;created_ns,&amp;amp;#32;then&amp;amp;#32;event&amp;amp;#32;ID.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;Legacy&amp;amp;#32;second-resolution&amp;amp;#32;timestamps&amp;amp;#32;are&amp;amp;#32;permitted&amp;amp;#32;only&amp;amp;#32;when&amp;amp;#32;the&amp;amp;#32;same&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;content&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;contradictory&amp;amp;#32;labels&amp;amp;#32;at&amp;amp;#32;an&amp;amp;#32;identical&amp;amp;#32;timestamp.&amp;amp;#32;Such&amp;amp;#32;a&amp;amp;#32;tie&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cannot&amp;amp;#32;be&amp;amp;#32;resolved&amp;amp;#32;from&amp;amp;#32;random&amp;amp;#32;event&amp;amp;#32;IDs&amp;amp;#32;and&amp;amp;#32;is&amp;amp;#32;quarantined&amp;amp;#32;without&amp;amp;#32;learning.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;pending&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows&amp;amp;#32;=&amp;amp;#32;[]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(pending)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;pending&amp;amp;#32;queue&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(pending&amp;amp;#32;/&amp;amp;#32;entry.name)&amp;amp;#32;as&amp;amp;#32;directory:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;json.loads(directory.read(&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;16384))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata,&amp;amp;#32;dict):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;metadata&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata&amp;amp;#32;=&amp;amp;#32;dict(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;metadata.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name&amp;amp;#32;or&amp;amp;#32;metadata.get(&amp;amp;quot;schema&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;event&amp;amp;#32;identity&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(metadata.get(&amp;amp;quot;sha256&amp;amp;quot;),&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(metadata[&amp;amp;quot;sha256&amp;amp;quot;]):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;ValueError(&amp;amp;quot;invalid&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.collector.validate_event(metadata.get(&amp;amp;quot;label&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;user&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;),&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata.get(&amp;amp;quot;source&amp;amp;quot;),&amp;amp;#32;metadata.get(&amp;amp;quot;destination&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;timestamp&amp;amp;#32;=&amp;amp;#32;metadata.get(&amp;amp;quot;created_ns&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;int)&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;isinstance(timestamp,&amp;amp;#32;bool)&amp;amp;#32;and&amp;amp;#32;timestamp&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;timestamp&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;metadata:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(metadata.get(&amp;amp;quot;created_utc&amp;amp;quot;))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_order_ns&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append(metadata)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;A&amp;amp;#32;manual&amp;amp;#32;administrator&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;archived&amp;amp;#32;this&amp;amp;#32;candidate.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;rows.append({&amp;amp;quot;id&amp;amp;quot;:&amp;amp;#32;entry.name,&amp;amp;#32;&amp;amp;quot;_invalid&amp;amp;quot;:&amp;amp;#32;True,&amp;amp;#32;&amp;amp;quot;_order_ns&amp;amp;quot;:&amp;amp;#32;0})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied&amp;amp;#32;=&amp;amp;#32;{}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;tied.setdefault((row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;]),&amp;amp;#32;set()).add(row[&amp;amp;quot;label&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;row&amp;amp;#32;in&amp;amp;#32;rows:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;row.get(&amp;amp;quot;_invalid&amp;amp;quot;)&amp;amp;#32;and&amp;amp;#32;len(tied[(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;sha256&amp;amp;quot;])])&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;1:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;row[&amp;amp;quot;_ordering_invalid&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;sorted(rows,&amp;amp;#32;key=lambda&amp;amp;#32;row:&amp;amp;#32;(row[&amp;amp;quot;_order_ns&amp;amp;quot;],&amp;amp;#32;row[&amp;amp;quot;id&amp;amp;quot;]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_journal(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Read&amp;amp;#32;root-owned&amp;amp;#32;receipts;&amp;amp;#32;successful&amp;amp;#32;classes&amp;amp;#32;use&amp;amp;#32;durable&amp;amp;#32;worker&amp;amp;#32;sequence.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;processed&amp;amp;#32;=&amp;amp;#32;Path(root)&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked&amp;amp;#32;=&amp;amp;#32;{},&amp;amp;#32;set()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;used_bytes&amp;amp;#32;=&amp;amp;#32;count&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;processed.exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(processed)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;os.scandir(processed)&amp;amp;#32;as&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;entry&amp;amp;#32;in&amp;amp;#32;entries:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;admin.ID_RE.fullmatch(entry.name):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;count&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;MAX_JOURNAL_EVENTS:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;processed&amp;amp;#32;journal&amp;amp;#32;scan&amp;amp;#32;bound&amp;amp;#32;exceeded&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;directory&amp;amp;#32;=&amp;amp;#32;processed&amp;amp;#32;/&amp;amp;#32;entry.name&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.receipt_for(directory)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(receipt,&amp;amp;#32;dict)&amp;amp;#32;or&amp;amp;#32;receipt.get(&amp;amp;quot;id&amp;amp;quot;)&amp;amp;#32;!=&amp;amp;#32;entry.name:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;receipt&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;sha256&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(digest,&amp;amp;#32;str)&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;SHA_RE.fullmatch(digest):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;digest&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;worker_sequence&amp;amp;quot;,&amp;amp;#32;0)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;isinstance(sequence,&amp;amp;#32;int)&amp;amp;#32;or&amp;amp;#32;isinstance(sequence,&amp;amp;#32;bool)&amp;amp;#32;or&amp;amp;#32;sequence&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;processed&amp;amp;#32;sequence&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;max_sequence&amp;amp;#32;=&amp;amp;#32;max(max_sequence,&amp;amp;#32;sequence)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;status&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;in&amp;amp;#32;UNCERTAIN:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label&amp;amp;#32;=&amp;amp;#32;receipt.get(&amp;amp;quot;selected_class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;label&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;ham&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;learned&amp;amp;#32;class&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Existing&amp;amp;#32;manual&amp;amp;#32;receipts&amp;amp;#32;predate&amp;amp;#32;automatic&amp;amp;#32;processing;&amp;amp;#32;the&amp;amp;#32;old&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;admin&amp;amp;#32;refuses&amp;amp;#32;further&amp;amp;#32;training&amp;amp;#32;of&amp;amp;#32;a&amp;amp;#32;SHA&amp;amp;#32;once&amp;amp;#32;it&amp;amp;#32;has&amp;amp;#32;a&amp;amp;#32;receipt.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fallback&amp;amp;#32;=&amp;amp;#32;_timestamp_ns(receipt.get(&amp;amp;quot;finished_utc&amp;amp;quot;,&amp;amp;#32;receipt.get(&amp;amp;quot;reviewed_utc&amp;amp;quot;)))&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;position&amp;amp;#32;=&amp;amp;#32;(bool(sequence),&amp;amp;#32;sequence&amp;amp;#32;if&amp;amp;#32;sequence&amp;amp;#32;else&amp;amp;#32;fallback,&amp;amp;#32;entry.name)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;or&amp;amp;#32;position&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;latest[digest][0]:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;(position,&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Count&amp;amp;#32;snapshots&amp;amp;#32;and&amp;amp;#32;preserved&amp;amp;#32;originals,&amp;amp;#32;never&amp;amp;#32;recursively&amp;amp;#32;walk&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;arbitrary&amp;amp;#32;files&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;mail-user-owned&amp;amp;#32;source/&amp;amp;#32;directory.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;part&amp;amp;#32;in&amp;amp;#32;(directory,&amp;amp;#32;directory&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;source&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.Directory(part)&amp;amp;#32;as&amp;amp;#32;safe:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;name&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;message.eml&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;metadata.json&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;admin.json&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;safe.fd&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;os.stat(name,&amp;amp;#32;dir_fd=safe.fd,&amp;amp;#32;follow_symlinks=False)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;(part&amp;amp;#32;/&amp;amp;#32;name).lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;stat.S_ISREG(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;info.st_size&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;max_sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;_check_trusted_parent_chain(path):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;parent&amp;amp;#32;in&amp;amp;#32;Path(path).parents:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;link_info&amp;amp;#32;=&amp;amp;#32;parent.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;parent.stat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(link_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISDIR(info.st_mode)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;or&amp;amp;#32;info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;executable&amp;amp;#32;parent&amp;amp;#32;directories&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;controlled&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;resolve_executable(executable):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Allow&amp;amp;#32;FreeBSD&amp;amp;#32;package&amp;amp;#32;symlinks,&amp;amp;#32;but&amp;amp;#32;pin&amp;amp;#32;both&amp;amp;#32;operations&amp;amp;#32;to&amp;amp;#32;their&amp;amp;#32;target.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source&amp;amp;#32;=&amp;amp;#32;Path(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;source.is_absolute():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;executable&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;absolute&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;source_info&amp;amp;#32;=&amp;amp;#32;source.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;source_info.st_uid&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;entry&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;owned&amp;amp;#32;by&amp;amp;#32;root&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(source)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;target&amp;amp;#32;=&amp;amp;#32;source.resolve(strict=True)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;target.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;a&amp;amp;#32;regular&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o022):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;rspamc&amp;amp;#32;target&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;and&amp;amp;#32;not&amp;amp;#32;writable&amp;amp;#32;by&amp;amp;#32;mail&amp;amp;#32;users&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;_check_trusted_parent_chain(target)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;str(target)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;controller_ready(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Authenticated&amp;amp;#32;read-only&amp;amp;#32;controller&amp;amp;#32;probe;&amp;amp;#32;no&amp;amp;#32;candidate&amp;amp;#32;is&amp;amp;#32;archived&amp;amp;#32;yet.&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command&amp;amp;#32;=&amp;amp;#32;[resolve_executable(executable),&amp;amp;#32;&amp;amp;quot;-h&amp;amp;quot;,&amp;amp;#32;controller]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;password_file:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;secret&amp;amp;#32;=&amp;amp;#32;Path(password_file)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;info&amp;amp;#32;=&amp;amp;#32;secret.lstat()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;secret.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;stat.S_ISREG(info.st_mode)&amp;amp;#32;or&amp;amp;#32;stat.S_ISLNK(info.st_mode):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;unsafe&amp;amp;#32;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;&amp;amp;#32;and&amp;amp;#32;(info.st_uid&amp;amp;#32;!=&amp;amp;#32;0&amp;amp;#32;or&amp;amp;#32;info.st_mode&amp;amp;#32;&amp;amp;amp;&amp;amp;#32;0o077):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;controller&amp;amp;#32;password&amp;amp;#32;file&amp;amp;#32;must&amp;amp;#32;be&amp;amp;#32;root-owned&amp;amp;#32;mode&amp;amp;#32;0600&amp;amp;#32;or&amp;amp;#32;stricter&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.extend([&amp;amp;quot;-P&amp;amp;quot;,&amp;amp;#32;str(secret)])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;command.append(&amp;amp;quot;stat&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;subprocess.run(command,&amp;amp;#32;stdin=subprocess.DEVNULL,&amp;amp;#32;stdout=subprocess.DEVNULL,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stderr=subprocess.DEVNULL,&amp;amp;#32;check=False,&amp;amp;#32;timeout=min(timeout,&amp;amp;#32;5)).returncode&amp;amp;#32;==&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;process_pending(root,&amp;amp;#32;*,&amp;amp;#32;executable=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;,&amp;amp;#32;controller=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=admin.DEFAULT_PASSWORD_FILE,&amp;amp;#32;timeout=30,&amp;amp;#32;batch_size=20,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight=None,&amp;amp;#32;learner=None,&amp;amp;#32;stop_event=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;Process&amp;amp;#32;at&amp;amp;#32;most&amp;amp;#32;batch_size&amp;amp;#32;candidates.&amp;amp;#32;Inject&amp;amp;#32;probes/learner&amp;amp;#32;for&amp;amp;#32;unit&amp;amp;#32;tests.&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;One&amp;amp;#32;admin&amp;amp;#32;lock&amp;amp;#32;serializes&amp;amp;#32;manual&amp;amp;#32;and&amp;amp;#32;background&amp;amp;#32;operations.&amp;amp;#32;A&amp;amp;#32;crash&amp;amp;#32;after&amp;amp;#32;a&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;durable&amp;amp;#32;learning&amp;amp;#32;intent&amp;amp;#32;leaves&amp;amp;#32;status=learning,&amp;amp;#32;which&amp;amp;#32;blocks&amp;amp;#32;this&amp;amp;#32;SHA&amp;amp;#32;on&amp;amp;#32;the&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;next&amp;amp;#32;pass;&amp;amp;#32;subsequent&amp;amp;#32;candidates&amp;amp;#32;are&amp;amp;#32;preserved&amp;amp;#32;as&amp;amp;#32;quarantined_uncertain.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;preflight&amp;amp;#32;=&amp;amp;#32;controller_ready&amp;amp;#32;if&amp;amp;#32;preflight&amp;amp;#32;is&amp;amp;#32;None&amp;amp;#32;else&amp;amp;#32;preflight&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;learner&amp;amp;#32;is&amp;amp;#32;None:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;FreeBSD&amp;amp;#32;pkg&amp;amp;#32;installs&amp;amp;#32;rspamc&amp;amp;#32;as&amp;amp;#32;a&amp;amp;#32;root-owned&amp;amp;#32;versioned&amp;amp;#32;symlink.&amp;amp;#32;Resolve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;it&amp;amp;#32;once&amp;amp;#32;so&amp;amp;#32;preflight&amp;amp;#32;and&amp;amp;#32;training&amp;amp;#32;invoke&amp;amp;#32;the&amp;amp;#32;same&amp;amp;#32;trusted&amp;amp;#32;executable.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;executable&amp;amp;#32;=&amp;amp;#32;resolve_executable(executable)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;learner&amp;amp;#32;=&amp;amp;#32;admin.run_learning&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;{&amp;amp;quot;learned&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;invalid&amp;amp;quot;:&amp;amp;#32;0,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;:&amp;amp;#32;0,&amp;amp;#32;&amp;amp;quot;controller_unavailable&amp;amp;quot;:&amp;amp;#32;False,&amp;amp;#32;&amp;amp;quot;storage_blocked&amp;amp;quot;:&amp;amp;#32;False,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;archive_warning&amp;amp;quot;:&amp;amp;#32;False}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;with&amp;amp;#32;admin.admin_lock(root):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;events&amp;amp;#32;=&amp;amp;#32;order_events(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;len(events)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest,&amp;amp;#32;blocked,&amp;amp;#32;sequence,&amp;amp;#32;used_bytes,&amp;amp;#32;archive_count&amp;amp;#32;=&amp;amp;#32;_journal(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;preflight(executable,&amp;amp;#32;controller,&amp;amp;#32;password_file,&amp;amp;#32;timeout):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;=&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;candidate&amp;amp;#32;in&amp;amp;#32;events:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;handled&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;batch_size&amp;amp;#32;or&amp;amp;#32;(stop_event&amp;amp;#32;is&amp;amp;#32;not&amp;amp;#32;None&amp;amp;#32;and&amp;amp;#32;stop_event.is_set()):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;event_id&amp;amp;#32;=&amp;amp;#32;candidate[&amp;amp;quot;id&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;processed&amp;amp;quot;&amp;amp;#32;/&amp;amp;#32;event_id).exists():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Crash&amp;amp;#32;before&amp;amp;#32;prepare_archive&amp;amp;#32;moved&amp;amp;#32;pending/ID:&amp;amp;#32;its&amp;amp;#32;root-owned&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;intent&amp;amp;#32;is&amp;amp;#32;authoritative.&amp;amp;#32;Preserve&amp;amp;#32;the&amp;amp;#32;pending&amp;amp;#32;original&amp;amp;#32;for&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;manual&amp;amp;#32;recovery&amp;amp;#32;and&amp;amp;#32;continue&amp;amp;#32;independent&amp;amp;#32;content,&amp;amp;#32;never&amp;amp;#32;retry.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;metadata,&amp;amp;#32;message&amp;amp;#32;=&amp;amp;#32;admin.read_event(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;pending&amp;amp;quot;,&amp;amp;#32;event_id)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;FileNotFoundError:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Do&amp;amp;#32;not&amp;amp;#32;act&amp;amp;#32;if&amp;amp;#32;the&amp;amp;#32;queued&amp;amp;#32;ordering/identity&amp;amp;#32;changed&amp;amp;#32;since&amp;amp;#32;enumeration.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;any(metadata.get(key)&amp;amp;#32;!=&amp;amp;#32;candidate.get(key)&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;(&amp;amp;quot;sha256&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;label&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_ns&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;created_utc&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;invalid&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;continue&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;used_bytes&amp;amp;#32;+&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&amp;amp;#32;&amp;amp;gt;&amp;amp;#32;admin.collector.MAX_PENDING_BYTES:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;label,&amp;amp;#32;digest&amp;amp;#32;=&amp;amp;#32;metadata[&amp;amp;quot;label&amp;amp;quot;],&amp;amp;#32;metadata[&amp;amp;quot;sha256&amp;amp;quot;]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;candidate.get(&amp;amp;quot;_ordering_invalid&amp;amp;quot;):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_ordering&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;event&amp;amp;#32;ordering&amp;amp;#32;is&amp;amp;#32;ambiguous&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;blocked:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;quarantined_uncertain&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;a&amp;amp;#32;prior&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;#32;has&amp;amp;#32;an&amp;amp;#32;uncertain&amp;amp;#32;result&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest&amp;amp;#32;and&amp;amp;#32;latest[digest][1]&amp;amp;#32;==&amp;amp;#32;label:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;same&amp;amp;#32;class&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;latest&amp;amp;#32;successful&amp;amp;#32;learning&amp;amp;#32;operation&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;status,&amp;amp;#32;reason&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;automatic&amp;amp;#32;explicit&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive,&amp;amp;#32;receipt&amp;amp;#32;=&amp;amp;#32;admin.prepare_archive(root,&amp;amp;#32;event_id,&amp;amp;#32;metadata,&amp;amp;#32;message,&amp;amp;#32;status,&amp;amp;#32;label,&amp;amp;#32;reason)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;prepare_archive&amp;amp;#32;writes&amp;amp;#32;a&amp;amp;#32;root&amp;amp;#32;journal&amp;amp;#32;before&amp;amp;#32;moving&amp;amp;#32;the&amp;amp;#32;source;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;stop&amp;amp;#32;instead&amp;amp;#32;of&amp;amp;#32;retrying&amp;amp;#32;an&amp;amp;#32;incompletely&amp;amp;#32;prepared&amp;amp;#32;operation.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;True&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sequence&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt.update({&amp;amp;quot;mode&amp;amp;quot;:&amp;amp;#32;&amp;amp;quot;automatic_imap_feedback&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;worker_sequence&amp;amp;quot;:&amp;amp;#32;sequence,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;processed_ns&amp;amp;quot;:&amp;amp;#32;time.time_ns(),&amp;amp;#32;&amp;amp;quot;source_created_ns&amp;amp;quot;:&amp;amp;#32;candidate[&amp;amp;quot;_order_ns&amp;amp;quot;]})&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;digest&amp;amp;#32;in&amp;amp;#32;latest:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;previous_successful_class&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;latest[digest][1]&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Durable&amp;amp;#32;intent&amp;amp;#32;exists&amp;amp;#32;before&amp;amp;#32;the&amp;amp;#32;only&amp;amp;#32;call&amp;amp;#32;that&amp;amp;#32;may&amp;amp;#32;change&amp;amp;#32;Bayes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learning&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;returncode&amp;amp;#32;=&amp;amp;#32;learner(message,&amp;amp;#32;label,&amp;amp;#32;executable=executable,&amp;amp;#32;controller=controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=password_file,&amp;amp;#32;timeout=timeout)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(subprocess.TimeoutExpired,&amp;amp;#32;OSError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;failure;&amp;amp;#32;server&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;learned;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;returncode&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;returncode&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;==&amp;amp;#32;0&amp;amp;#32;else&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;returncode&amp;amp;#32;!=&amp;amp;#32;0:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;error&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;client&amp;amp;#32;returned&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;receipt[&amp;amp;quot;finished_utc&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;admin.collector.utc_now()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.update_receipt(archive,&amp;amp;#32;receipt)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learned&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;latest[digest]&amp;amp;#32;=&amp;amp;#32;((True,&amp;amp;#32;sequence,&amp;amp;#32;event_id),&amp;amp;#32;label)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;learned&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;elif&amp;amp;#32;status&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;duplicate&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;duplicates&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;else:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;blocked.add(digest)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;quarantined&amp;amp;quot;]&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;handled&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;used_bytes&amp;amp;#32;+=&amp;amp;#32;2&amp;amp;#32;*&amp;amp;#32;len(message)&amp;amp;#32;+&amp;amp;#32;32768&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;archive_count&amp;amp;#32;+=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;pending&amp;amp;quot;]&amp;amp;#32;-=&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary[&amp;amp;quot;archive_warning&amp;amp;quot;]&amp;amp;#32;=&amp;amp;#32;archive_count&amp;amp;#32;&amp;amp;gt;=&amp;amp;#32;admin.collector.MAX_PENDING_EVENTS&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;feedback_processed&amp;amp;quot;,&amp;amp;#32;id=event_id,&amp;amp;#32;label=label,&amp;amp;#32;status=receipt[&amp;amp;quot;status&amp;amp;quot;])&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;receipt[&amp;amp;quot;status&amp;amp;quot;]&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;learn_uncertain&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;The&amp;amp;#32;controller&amp;amp;#32;may&amp;amp;#32;have&amp;amp;#32;failed&amp;amp;#32;after&amp;amp;#32;the&amp;amp;#32;preflight.&amp;amp;#32;Leave&amp;amp;#32;all&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;other&amp;amp;#32;candidates&amp;amp;#32;pending&amp;amp;#32;until&amp;amp;#32;a&amp;amp;#32;fresh&amp;amp;#32;read-only&amp;amp;#32;probe&amp;amp;#32;passes.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;break&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def&amp;amp;#32;main(argv=None):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser&amp;amp;#32;=&amp;amp;#32;argparse.ArgumentParser(description=__doc__)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--queue-root&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_ROOT)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--rspamc-bin&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;/usr/local/bin/rspamc&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--controller&amp;amp;quot;,&amp;amp;#32;default=&amp;amp;quot;127.0.0.1:11334&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--password-file&amp;amp;quot;,&amp;amp;#32;default=admin.DEFAULT_PASSWORD_FILE)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--poll&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=2)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--timeout&amp;amp;quot;,&amp;amp;#32;type=float,&amp;amp;#32;default=30)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--batch-size&amp;amp;quot;,&amp;amp;#32;type=int,&amp;amp;#32;default=20)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;parser.add_argument(&amp;amp;quot;--once&amp;amp;quot;,&amp;amp;#32;action=&amp;amp;quot;store_true&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;args&amp;amp;#32;=&amp;amp;#32;parser.parse_args(argv)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.umask(0o077)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.require_root()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;root&amp;amp;#32;=&amp;amp;#32;Path(args.queue_root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;not&amp;amp;#32;root.is_absolute()&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0.2&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.poll&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;60&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;0&amp;amp;#32;&amp;amp;lt;&amp;amp;#32;args.timeout&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;300&amp;amp;#32;or&amp;amp;#32;not&amp;amp;#32;1&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;args.batch_size&amp;amp;#32;&amp;amp;lt;=&amp;amp;#32;100:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;FeedbackError(&amp;amp;quot;invalid&amp;amp;#32;worker&amp;amp;#32;options&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;admin.check_root_directory(root)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping&amp;amp;#32;=&amp;amp;#32;threading.Event()&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;for&amp;amp;#32;signum&amp;amp;#32;in&amp;amp;#32;(signal.SIGTERM,&amp;amp;#32;signal.SIGINT):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;signal.signal(signum,&amp;amp;#32;lambda&amp;amp;#32;*_:&amp;amp;#32;stopping.set())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;lockfd&amp;amp;#32;=&amp;amp;#32;os.open(root&amp;amp;#32;/&amp;amp;#32;&amp;amp;quot;.worker.lock&amp;amp;quot;,&amp;amp;#32;os.O_RDWR&amp;amp;#32;|&amp;amp;#32;os.O_CREAT&amp;amp;#32;|&amp;amp;#32;getattr(os,&amp;amp;#32;&amp;amp;quot;O_NOFOLLOW&amp;amp;quot;,&amp;amp;#32;0),&amp;amp;#32;0o600)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;os.name&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;posix&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;import&amp;amp;#32;fcntl&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fcntl.flock(lockfd,&amp;amp;#32;fcntl.LOCK_EX&amp;amp;#32;|&amp;amp;#32;fcntl.LOCK_NB)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;None&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;while&amp;amp;#32;not&amp;amp;#32;stopping.is_set():&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;summary&amp;amp;#32;=&amp;amp;#32;process_pending(root,&amp;amp;#32;executable=args.rspamc_bin,&amp;amp;#32;controller=args.controller,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;password_file=args.password_file,&amp;amp;#32;timeout=args.timeout,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;batch_size=args.batch_size,&amp;amp;#32;stop_event=stopping)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;state&amp;amp;#32;=&amp;amp;#32;{key:&amp;amp;#32;value&amp;amp;#32;for&amp;amp;#32;key,&amp;amp;#32;value&amp;amp;#32;in&amp;amp;#32;summary.items()&amp;amp;#32;if&amp;amp;#32;key&amp;amp;#32;not&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;state&amp;amp;#32;!=&amp;amp;#32;previous_state&amp;amp;#32;or&amp;amp;#32;any(summary[key]&amp;amp;#32;for&amp;amp;#32;key&amp;amp;#32;in&amp;amp;#32;(&amp;amp;quot;learned&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;duplicates&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;quarantined&amp;amp;quot;)):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_state&amp;amp;quot;,&amp;amp;#32;**summary)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;state&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;2&amp;amp;#32;if&amp;amp;#32;summary[&amp;amp;quot;controller_unavailable&amp;amp;quot;]&amp;amp;#32;or&amp;amp;#32;summary[&amp;amp;quot;storage_blocked&amp;amp;quot;]&amp;amp;#32;else&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;previous_state&amp;amp;#32;!=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_error&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;local&amp;amp;#32;queue,&amp;amp;#32;journal&amp;amp;#32;or&amp;amp;#32;configuration&amp;amp;#32;failure;&amp;amp;#32;no&amp;amp;#32;automatic&amp;amp;#32;retry&amp;amp;#32;of&amp;amp;#32;uncertain&amp;amp;#32;training&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;previous_state&amp;amp;#32;=&amp;amp;#32;&amp;amp;quot;local_error&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;args.once:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stopping.wait(args.poll)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;0&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;finally:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;os.close(lockfd)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;__name__&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;__main__&amp;amp;quot;:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;try:&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(main())&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;except&amp;amp;#32;(OSError,&amp;amp;#32;ValueError,&amp;amp;#32;TypeError,&amp;amp;#32;FeedbackError):&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;log_event(&amp;amp;quot;worker_start_failed&amp;amp;quot;,&amp;amp;#32;reason=&amp;amp;quot;root,&amp;amp;#32;configuration&amp;amp;#32;or&amp;amp;#32;worker&amp;amp;#32;lock&amp;amp;#32;required&amp;amp;quot;)&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;raise&amp;amp;#32;SystemExit(1)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Обёртки запуска и права ====&lt;br /&gt;
Создать /usr/local/libexec/rspamd-feedback/collector:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Создать /usr/local/sbin/rspamd-feedback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
exec&amp;amp;#32;/usr/local/bin/python3.12&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;&amp;amp;quot;$@&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
chmod&amp;amp;#32;0755&amp;amp;#32;/usr/local/libexec/rspamd-feedback/*&amp;amp;#32;/usr/local/sbin/rspamd-feedback&lt;br /&gt;
python3.12&amp;amp;#32;-m&amp;amp;#32;py_compile&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-worker.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.5. Подключить IMAPSieve ===&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-feedback.conf как root:wheel 0644. Блок inbox дополняет существующий namespace, не заменяет его.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;2.4.5&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4:&amp;amp;#32;local&amp;amp;#32;IMAP&amp;amp;#32;feedback&amp;amp;#32;collection&amp;amp;#32;for&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;worker.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;end&amp;amp;#32;of&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;root:wheel&amp;amp;#32;0644.&amp;amp;#32;Precompile&amp;amp;#32;the&amp;amp;#32;two&amp;amp;#32;administrator&amp;amp;#32;scripts&amp;amp;#32;with&amp;amp;#32;sievec.&lt;br /&gt;
#&amp;amp;#32;No&amp;amp;#32;Sieve&amp;amp;#32;plugin&amp;amp;#32;is&amp;amp;#32;enabled&amp;amp;#32;for&amp;amp;#32;LMTP/LDA&amp;amp;#32;and&amp;amp;#32;no&amp;amp;#32;incoming&amp;amp;#32;mail&amp;amp;#32;is&amp;amp;#32;moved.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;imap&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imap_sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_imapsieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_extprograms&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;External&amp;amp;#32;commands&amp;amp;#32;are&amp;amp;#32;available&amp;amp;#32;only&amp;amp;#32;to&amp;amp;#32;administrator-controlled&amp;amp;#32;scripts.&lt;br /&gt;
sieve_global_extensions&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.pipe&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;vnd.dovecot.environment&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sieve_pipe_bin_dir&amp;amp;#32;=&amp;amp;#32;/usr/local/libexec/rspamd-feedback&lt;br /&gt;
sieve_pipe_exec_timeout&amp;amp;#32;=&amp;amp;#32;3s&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;existing&amp;amp;#32;inbox&amp;amp;#32;namespace&amp;amp;#32;is&amp;amp;#32;private,&amp;amp;#32;has&amp;amp;#32;no&amp;amp;#32;prefix,&amp;amp;#32;and&amp;amp;#32;uses&amp;amp;#32;&amp;amp;#x27;/&amp;amp;#x27;&amp;amp;#32;as&amp;amp;#32;its&lt;br /&gt;
#&amp;amp;#32;separator.&amp;amp;#32;Exact&amp;amp;#32;names&amp;amp;#32;deliberately&amp;amp;#32;exclude&amp;amp;#32;shared&amp;amp;#32;namespaces,&amp;amp;#32;subfolders,&lt;br /&gt;
#&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;and&amp;amp;#32;all&amp;amp;#32;movements&amp;amp;#32;other&amp;amp;#32;than&amp;amp;#32;these&amp;amp;#32;two&amp;amp;#32;directions.&lt;br /&gt;
namespace&amp;amp;#32;inbox&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;auto&amp;amp;#32;=&amp;amp;#32;subscribe&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;special_use&amp;amp;#32;=&amp;amp;#32;\Junk&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-spam&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mailbox&amp;amp;#32;INBOX&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;imapsieve_from&amp;amp;#32;Junk&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-report-ham&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;copy&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;imapsieve_url&amp;amp;#32;is&amp;amp;#32;intentionally&amp;amp;#32;unset:&amp;amp;#32;no&amp;amp;#32;user-uploaded&amp;amp;#32;IMAPSieve&amp;amp;#32;scripts.&lt;br /&gt;
#&amp;amp;#32;COPY&amp;amp;#32;is&amp;amp;#32;also&amp;amp;#32;the&amp;amp;#32;IMAPSieve&amp;amp;#32;cause&amp;amp;#32;for&amp;amp;#32;a&amp;amp;#32;client-issued&amp;amp;#32;IMAP&amp;amp;#32;MOVE&amp;amp;#32;command.&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;spools&amp;amp;#32;reports;&amp;amp;#32;the&amp;amp;#32;separate&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;trains&amp;amp;#32;Bayes&amp;amp;#32;automatically.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конце основного /usr/local/etc/dovecot/dovecot.conf добавить ровно одну строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-spam.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Recheck&amp;amp;#32;the&amp;amp;#32;event&amp;amp;#32;as&amp;amp;#32;well&amp;amp;#32;as&amp;amp;#32;the&amp;amp;#32;Dovecot&amp;amp;#32;configuration&amp;amp;#32;filters.&amp;amp;#32;This&amp;amp;#32;excludes&lt;br /&gt;
#&amp;amp;#32;APPEND/FLAG,&amp;amp;#32;shared-folder&amp;amp;#32;paths,&amp;amp;#32;subfolders&amp;amp;#32;and&amp;amp;#32;non-INBOX&amp;amp;#32;source&amp;amp;#32;mailboxes.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Fixed&amp;amp;#32;program&amp;amp;#32;name;&amp;amp;#32;all&amp;amp;#32;metadata&amp;amp;#32;are&amp;amp;#32;separate&amp;amp;#32;argv&amp;amp;#32;entries,&amp;amp;#32;never&amp;amp;#32;shell&amp;amp;#32;text.&lt;br /&gt;
#&amp;amp;#32;:copy&amp;amp;#32;preserves&amp;amp;#32;the&amp;amp;#32;message;&amp;amp;#32;:try&amp;amp;#32;keeps&amp;amp;#32;feedback&amp;amp;#32;failures&amp;amp;#32;non-fatal.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;spam&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/usr/local/etc/dovecot/rspamd/report-ham.sieve:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;vnd.dovecot.pipe&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;copy&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;vnd.dovecot.imapsieve&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;environment&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;variables&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Only&amp;amp;#32;restoring&amp;amp;#32;a&amp;amp;#32;message&amp;amp;#32;from&amp;amp;#32;Junk&amp;amp;#32;to&amp;amp;#32;INBOX&amp;amp;#32;constitutes&amp;amp;#32;a&amp;amp;#32;ham&amp;amp;#32;report.&lt;br /&gt;
#&amp;amp;#32;Moving&amp;amp;#32;to&amp;amp;#32;Trash/Sent/Archive,&amp;amp;#32;APPEND,&amp;amp;#32;FLAG,&amp;amp;#32;and&amp;amp;#32;shared&amp;amp;#32;folders&amp;amp;#32;are&amp;amp;#32;ignored.&lt;br /&gt;
if&amp;amp;#32;not&amp;amp;#32;allof&amp;amp;#32;(&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;imap.cause&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;COPY&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-from&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;environment&amp;amp;#32;:is&amp;amp;#32;:comparator&amp;amp;#32;&amp;amp;quot;i;octet&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;vnd.dovecot.mailbox-to&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;&lt;br /&gt;
)&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if&amp;amp;#32;environment&amp;amp;#32;:matches&amp;amp;#32;&amp;amp;quot;imap.user&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;*&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;set&amp;amp;#32;&amp;amp;quot;username&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;${1}&amp;amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
if&amp;amp;#32;string&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;The&amp;amp;#32;collector&amp;amp;#32;stores&amp;amp;#32;a&amp;amp;#32;pending&amp;amp;#32;report;&amp;amp;#32;the&amp;amp;#32;background&amp;amp;#32;root&amp;amp;#32;worker&amp;amp;#32;learns&amp;amp;#32;it.&lt;br /&gt;
pipe&amp;amp;#32;:copy&amp;amp;#32;:try&amp;amp;#32;&amp;amp;quot;collector&amp;amp;quot;&amp;amp;#32;[&amp;amp;quot;ham&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;${username}&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;INBOX&amp;amp;quot;];&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd-feedback.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.sieve&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-spam.sieve&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/report-ham.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/*.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Рестарт Dovecot кратковременно разрывает существующие IMAP-сеансы; клиенты подключатся заново. Sieve :copy сохраняет письмо, :try не делает ошибку сборщика причиной отказа пользовательского действия. Выполнение внешнего сборщика ограничено 3 секундами, ожидание его блокировки — 2 секундами. LMTP-сортировка не нужна для обучения и на этом шаге ещё не включена.&lt;br /&gt;
&lt;br /&gt;
=== 15.6. Запустить автоматический обработчик ===&lt;br /&gt;
Сохранить /usr/local/etc/rc.d/rspamd_feedback как root:wheel 0555:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;PROVIDE:&amp;amp;#32;rspamd_feedback&lt;br /&gt;
#&amp;amp;#32;REQUIRE:&amp;amp;#32;LOGIN&amp;amp;#32;redis&amp;amp;#32;rspamd&lt;br /&gt;
#&amp;amp;#32;KEYWORD:&amp;amp;#32;shutdown&lt;br /&gt;
#&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;as&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback,&amp;amp;#32;root:wheel&amp;amp;#32;0555.&lt;br /&gt;
#&amp;amp;#32;Enable&amp;amp;#32;with:&amp;amp;#32;sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
#&amp;amp;#32;Uses&amp;amp;#32;FreeBSD&amp;amp;#32;15.1&amp;amp;#32;daemon(8):&amp;amp;#32;rc.subr&amp;amp;#32;targets&amp;amp;#32;the&amp;amp;#32;supervisor&amp;amp;#32;PID&amp;amp;#32;(-P),&lt;br /&gt;
#&amp;amp;#32;not&amp;amp;#32;the&amp;amp;#32;child&amp;amp;#32;PID&amp;amp;#32;(-p),&amp;amp;#32;so&amp;amp;#32;stop&amp;amp;#32;cannot&amp;amp;#32;accidentally&amp;amp;#32;restart&amp;amp;#32;the&amp;amp;#32;worker.&lt;br /&gt;
&lt;br /&gt;
.&amp;amp;#32;/etc/rc.subr&lt;br /&gt;
&lt;br /&gt;
name=&amp;amp;quot;rspamd_feedback&amp;amp;quot;&lt;br /&gt;
desc=&amp;amp;quot;Apply&amp;amp;#32;user&amp;amp;#32;spam/ham&amp;amp;#32;feedback&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;Bayes&amp;amp;#32;classifier&amp;amp;quot;&lt;br /&gt;
rcvar=&amp;amp;quot;rspamd_feedback_enable&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
load_rc_config&amp;amp;#32;&amp;amp;quot;${name}&amp;amp;quot;&lt;br /&gt;
:&amp;amp;#32;${rspamd_feedback_enable:=&amp;amp;quot;NO&amp;amp;quot;}&lt;br /&gt;
&lt;br /&gt;
command=&amp;amp;quot;/usr/sbin/daemon&amp;amp;quot;&lt;br /&gt;
procname=&amp;amp;quot;/usr/sbin/daemon&amp;amp;quot;&lt;br /&gt;
pidfile=&amp;amp;quot;/var/run/rspamd_feedback.pid&amp;amp;quot;&lt;br /&gt;
child_pidfile=&amp;amp;quot;/var/run/rspamd_feedback.child.pid&amp;amp;quot;&lt;br /&gt;
worker=&amp;amp;quot;/usr/local/libexec/rspamd-feedback/feedback-worker.py&amp;amp;quot;&lt;br /&gt;
python=&amp;amp;quot;/usr/local/bin/python3.12&amp;amp;quot;&lt;br /&gt;
logdir=&amp;amp;quot;/opt/mail/log/rspamd-feedback&amp;amp;quot;&lt;br /&gt;
logfile=&amp;amp;quot;${logdir}/worker.log&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
required_files=&amp;amp;quot;${python}&amp;amp;#32;${worker}&amp;amp;#32;/usr/local/libexec/rspamd-feedback/feedback-admin.py&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;quot;&lt;br /&gt;
required_dirs=&amp;amp;quot;/opt/mail/rspamd-feedback/pending&amp;amp;#32;/opt/mail/rspamd-feedback/processed&amp;amp;#32;/opt/mail/rspamd&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Worker&amp;amp;#32;handles&amp;amp;#32;transient&amp;amp;#32;controller&amp;amp;#32;errors&amp;amp;#32;in&amp;amp;#32;its&amp;amp;#32;polling&amp;amp;#32;loop.&amp;amp;#32;Unexpected&lt;br /&gt;
#&amp;amp;#32;exits&amp;amp;#32;get&amp;amp;#32;at&amp;amp;#32;most&amp;amp;#32;five&amp;amp;#32;supervised&amp;amp;#32;restarts,&amp;amp;#32;spaced&amp;amp;#32;15&amp;amp;#32;seconds&amp;amp;#32;apart.&lt;br /&gt;
#&amp;amp;#32;-H&amp;amp;#32;reopens&amp;amp;#32;the&amp;amp;#32;log&amp;amp;#32;on&amp;amp;#32;SIGHUP&amp;amp;#32;without&amp;amp;#32;interrupting&amp;amp;#32;an&amp;amp;#32;in-flight&amp;amp;#32;learn&amp;amp;#32;request.&lt;br /&gt;
command_args=&amp;amp;quot;-c&amp;amp;#32;-f&amp;amp;#32;-H&amp;amp;#32;-R&amp;amp;#32;15&amp;amp;#32;-C&amp;amp;#32;5&amp;amp;#32;-P&amp;amp;#32;${pidfile}&amp;amp;#32;-p&amp;amp;#32;${child_pidfile}&amp;amp;#32;-t&amp;amp;#32;${name}&amp;amp;#32;-o&amp;amp;#32;${logfile}&amp;amp;#32;${python}&amp;amp;#32;-u&amp;amp;#32;${worker}&amp;amp;#32;--poll&amp;amp;#32;2&amp;amp;quot;&lt;br /&gt;
start_precmd=&amp;amp;quot;rspamd_feedback_prestart&amp;amp;quot;&lt;br /&gt;
extra_commands=&amp;amp;quot;reload&amp;amp;quot;&lt;br /&gt;
sig_reload=&amp;amp;quot;HUP&amp;amp;quot;&lt;br /&gt;
&lt;br /&gt;
rspamd_feedback_prestart()&lt;br /&gt;
{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;&amp;amp;quot;$(id&amp;amp;#32;-u)&amp;amp;quot;&amp;amp;#32;-ne&amp;amp;#32;0&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn&amp;amp;#32;&amp;amp;quot;${name}&amp;amp;#32;requires&amp;amp;#32;root&amp;amp;#32;to&amp;amp;#32;read&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;controller&amp;amp;#32;credential&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;Refuse&amp;amp;#32;to&amp;amp;#32;write&amp;amp;#32;queue/log&amp;amp;#32;data&amp;amp;#32;to&amp;amp;#32;the&amp;amp;#32;root&amp;amp;#32;filesystem&amp;amp;#32;when&amp;amp;#32;/opt&amp;amp;#32;failed&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;#&amp;amp;#32;to&amp;amp;#32;mount.&amp;amp;#32;Directory&amp;amp;#32;existence&amp;amp;#32;alone&amp;amp;#32;does&amp;amp;#32;not&amp;amp;#32;establish&amp;amp;#32;the&amp;amp;#32;mount&amp;amp;#32;state.&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;!&amp;amp;#32;/sbin/mount&amp;amp;#32;-p&amp;amp;#32;|&amp;amp;#32;/usr/bin/awk&amp;amp;#32;&amp;amp;#x27;$2&amp;amp;#32;==&amp;amp;#32;&amp;amp;quot;/opt&amp;amp;quot;&amp;amp;#32;{&amp;amp;#32;found&amp;amp;#32;=&amp;amp;#32;1&amp;amp;#32;}&amp;amp;#32;END&amp;amp;#32;{&amp;amp;#32;exit&amp;amp;#32;!found&amp;amp;#32;}&amp;amp;#x27;;&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn&amp;amp;#32;&amp;amp;quot;${name}&amp;amp;#32;requires&amp;amp;#32;the&amp;amp;#32;mail&amp;amp;#32;filesystem&amp;amp;#32;mounted&amp;amp;#32;at&amp;amp;#32;/opt&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;${logdir}&amp;amp;quot;&amp;amp;#32;]&amp;amp;#32;||&amp;amp;#32;[&amp;amp;#32;-L&amp;amp;#32;&amp;amp;quot;${logfile}&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;warn&amp;amp;#32;&amp;amp;quot;${name}&amp;amp;#32;refuses&amp;amp;#32;a&amp;amp;#32;symlink&amp;amp;#32;at&amp;amp;#32;its&amp;amp;#32;private&amp;amp;#32;log&amp;amp;#32;path&amp;amp;quot;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/usr/bin/install&amp;amp;#32;-d&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0700&amp;amp;#32;&amp;amp;quot;${logdir}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;if&amp;amp;#32;[&amp;amp;#32;!&amp;amp;#32;-e&amp;amp;#32;&amp;amp;quot;${logfile}&amp;amp;quot;&amp;amp;#32;];&amp;amp;#32;then&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/usr/bin/install&amp;amp;#32;-o&amp;amp;#32;root&amp;amp;#32;-g&amp;amp;#32;wheel&amp;amp;#32;-m&amp;amp;#32;0600&amp;amp;#32;/dev/null&amp;amp;#32;&amp;amp;quot;${logfile}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fi&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/usr/sbin/chown&amp;amp;#32;root:wheel&amp;amp;#32;&amp;amp;quot;${logfile}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;/bin/chmod&amp;amp;#32;0600&amp;amp;#32;&amp;amp;quot;${logfile}&amp;amp;quot;&amp;amp;#32;||&amp;amp;#32;return&amp;amp;#32;1&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;umask&amp;amp;#32;077&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
run_rc_command&amp;amp;#32;&amp;amp;quot;$1&amp;amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&amp;amp;#32;root:wheel&amp;amp;#32;0644.&lt;br /&gt;
#&amp;amp;#32;FreeBSD&amp;amp;#32;daemon&amp;amp;#32;-H&amp;amp;#32;receives&amp;amp;#32;SIGHUP&amp;amp;#32;through&amp;amp;#32;its&amp;amp;#32;supervisor&amp;amp;#32;pidfile&amp;amp;#32;and&amp;amp;#32;reopens&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;file.&amp;amp;#32;Keep&amp;amp;#32;seven&amp;amp;#32;compressed&amp;amp;#32;1&amp;amp;#32;MiB&amp;amp;#32;archives;&amp;amp;#32;active&amp;amp;#32;and&amp;amp;#32;rotated&amp;amp;#32;logs&amp;amp;#32;0600.&lt;br /&gt;
/opt/mail/log/rspamd-feedback/worker.log&amp;amp;#32;root:wheel&amp;amp;#32;600&amp;amp;#32;7&amp;amp;#32;1024&amp;amp;#32;*&amp;amp;#32;JC&amp;amp;#32;/var/run/rspamd_feedback.pid&amp;amp;#32;1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
chmod&amp;amp;#32;0555&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
sh&amp;amp;#32;-n&amp;amp;#32;/usr/local/etc/rc.d/rspamd_feedback&lt;br /&gt;
newsyslog&amp;amp;#32;-n&amp;amp;#32;-f&amp;amp;#32;/usr/local/etc/newsyslog.conf.d/rspamd-feedback.conf&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=YES&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;start&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Супервизор daemon имеет отдельные PID-файлы родителя и ребёнка: stop завершает супервизор, не вызывает немедленный перезапуск worker. После неожиданного выхода предусмотрено не более пяти перезапусков с паузой 15 секунд. Проверка монтирования /opt выполняется до открытия журналов.&lt;br /&gt;
&lt;br /&gt;
=== 15.7. Необязательно: автоматическая доставка отмеченного спама в Junk ===&lt;br /&gt;
Этот небольшой отдельный этап помещает новые письма с локальной X-Spam-Flag: YES в Junk. Остальная почта сохраняется во Входящих. Уже лежащие сообщения не перемещаются. Если нужна только обратная связь при ручных перемещениях, этот этап можно пропустить. Автоматическая LMTP-доставка в Junk сама по себе не создаёт IMAP COPY и не обучает фильтр; исправление пользователем Junk → INBOX создаст ham-событие.&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd-spam-delivery.conf как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#&amp;amp;#32;Dovecot&amp;amp;#32;CE&amp;amp;#32;/&amp;amp;#32;Pigeonhole&amp;amp;#32;2.4.5:&amp;amp;#32;sort&amp;amp;#32;marked&amp;amp;#32;mail&amp;amp;#32;during&amp;amp;#32;LMTP&amp;amp;#32;delivery&amp;amp;#32;only.&lt;br /&gt;
#&amp;amp;#32;Include&amp;amp;#32;explicitly&amp;amp;#32;from&amp;amp;#32;the&amp;amp;#32;existing&amp;amp;#32;monolithic&amp;amp;#32;dovecot.conf.&lt;br /&gt;
#&amp;amp;#32;Install&amp;amp;#32;this&amp;amp;#32;fragment&amp;amp;#32;and&amp;amp;#32;spam-delivery.sieve&amp;amp;#32;root:wheel&amp;amp;#32;0644,&amp;amp;#32;and&amp;amp;#32;precompile&lt;br /&gt;
#&amp;amp;#32;the&amp;amp;#32;script&amp;amp;#32;with&amp;amp;#32;sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf;&amp;amp;#32;make&amp;amp;#32;the&amp;amp;#32;resulting&lt;br /&gt;
#&amp;amp;#32;.svbin&amp;amp;#32;root:wheel&amp;amp;#32;0644&amp;amp;#32;so&amp;amp;#32;the&amp;amp;#32;vmail&amp;amp;#32;LMTP&amp;amp;#32;process&amp;amp;#32;can&amp;amp;#32;read&amp;amp;#32;it.&lt;br /&gt;
&lt;br /&gt;
protocol&amp;amp;#32;lmtp&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;mail_plugins&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;sieve&amp;amp;#32;=&amp;amp;#32;yes&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;sieve_script&amp;amp;#32;rspamd-spam-delivery&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;type&amp;amp;#32;=&amp;amp;#32;before&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;cause&amp;amp;#32;=&amp;amp;#32;delivery&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;path&amp;amp;#32;=&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Existing&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;feedback&amp;amp;#32;scripts&amp;amp;#32;remain&amp;amp;#32;separate.&amp;amp;#32;LMTP&amp;amp;#32;fileinto&amp;amp;#32;does&amp;amp;#32;not&lt;br /&gt;
#&amp;amp;#32;produce&amp;amp;#32;an&amp;amp;#32;IMAP&amp;amp;#32;COPY&amp;amp;#32;event,&amp;amp;#32;so&amp;amp;#32;automatic&amp;amp;#32;filing&amp;amp;#32;cannot&amp;amp;#32;train&amp;amp;#32;the&amp;amp;#32;classifier.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Сохранить /usr/local/etc/dovecot/rspamd/spam-delivery.sieve как root:wheel 0644:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
require&amp;amp;#32;[&amp;amp;quot;fileinto&amp;amp;quot;,&amp;amp;#32;&amp;amp;quot;mailbox&amp;amp;quot;];&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Exim&amp;amp;#32;removes&amp;amp;#32;untrusted&amp;amp;#32;X-Spam-Flag&amp;amp;#32;and&amp;amp;#32;adds&amp;amp;#32;the&amp;amp;#32;local&amp;amp;#32;Rspamd&amp;amp;#32;result.&lt;br /&gt;
#&amp;amp;#32;This&amp;amp;#32;administrator&amp;amp;#32;script&amp;amp;#32;is&amp;amp;#32;used&amp;amp;#32;only&amp;amp;#32;for&amp;amp;#32;LMTP&amp;amp;#32;delivery,&amp;amp;#32;not&amp;amp;#32;IMAP&amp;amp;#32;events.&lt;br /&gt;
if&amp;amp;#32;header&amp;amp;#32;:is&amp;amp;#32;&amp;amp;quot;X-Spam-Flag&amp;amp;quot;&amp;amp;#32;&amp;amp;quot;YES&amp;amp;quot;&amp;amp;#32;{&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;fileinto&amp;amp;#32;:create&amp;amp;#32;&amp;amp;quot;Junk&amp;amp;quot;;&lt;br /&gt;
&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;&amp;amp;#32;stop;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#&amp;amp;#32;Unmarked&amp;amp;#32;messages&amp;amp;#32;use&amp;amp;#32;implicit&amp;amp;#32;keep&amp;amp;#32;(INBOX&amp;amp;#32;in&amp;amp;#32;the&amp;amp;#32;current&amp;amp;#32;configuration).&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конец основного dovecot.conf добавить единственную строку:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
!include&amp;amp;#32;/usr/local/etc/dovecot/rspamd-spam-delivery.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
doveconf&amp;amp;#32;-n&lt;br /&gt;
sievec&amp;amp;#32;-c&amp;amp;#32;/usr/local/etc/dovecot/dovecot.conf&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.sieve&lt;br /&gt;
chown&amp;amp;#32;root:wheel&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
chmod&amp;amp;#32;0644&amp;amp;#32;/usr/local/etc/dovecot/rspamd/spam-delivery.svbin&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;restart&lt;br /&gt;
service&amp;amp;#32;dovecot&amp;amp;#32;status&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.8. Сквозная проверка обучения ===&lt;br /&gt;
Использовать отдельный тестовый ящик из уже настроенного домена; войти в него полным адресом через IMAP или SnappyMail. В клиенте папка Junk должна иметь специальный флаг \Junk и может отображаться как «Спам». Если папки нет в списке — обновить список/подписку.&lt;br /&gt;
&lt;br /&gt;
# Проверить доставку обычного письма и локальные заголовки X-Spam-Score/X-Rspamd-Action. Поддельный X-Spam-Flag внешнего отправителя должен быть очищен и заменён результатом сервера.&lt;br /&gt;
# Отправить безвредное тестовое письмо себе. Переместить INBOX → Junk. В worker.log должен появиться feedback_processed с label=spam и status=learned, а событие перейти из pending в processed.&lt;br /&gt;
# Вернуть то же письмо Junk → INBOX: появится новое событие ham. Для проверки повторного исправления можно пройти spam → ham → spam → ham.&lt;br /&gt;
# Повтор той же копии с тем же классом должен иметь status=duplicate, не увеличивая вес примера повторно.&lt;br /&gt;
# Junk → Trash, изменение флагов и добавление письма сразу в папку не должны создавать обучающие события.&lt;br /&gt;
# Если включена сортировка, новая доставка в Junk должна обходиться без обратной связи; ручной возврат такого письма во Входящие должен дать ровно один ham-event.&lt;br /&gt;
# Отдельно проверить, что обычная почта доставляется при недоступном Rspamd, а ClamAV по-прежнему отклоняет EICAR. Остановку служб для этой проверки выполнять в согласованное окно или на тестовом экземпляре.&lt;br /&gt;
&lt;br /&gt;
Для тестов, которые не должны обучать рабочую Bayes-базу, нужен отдельный экземпляр Rspamd с изолированной Redis-базой. Примеры обычных пользователей в тестовую базу не копировать. Синтетические события после теста удаляют только вместе с явно выделенным тестовым ящиком/изолированной очередью.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;status&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&lt;br /&gt;
rspamd-feedback&amp;amp;#32;list&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
rspamc&amp;amp;#32;-h&amp;amp;#32;127.0.0.1:11334&amp;amp;#32;-P&amp;amp;#32;/usr/local/etc/rspamd/controller.password&amp;amp;#32;stat&lt;br /&gt;
tail&amp;amp;#32;-50&amp;amp;#32;/opt/mail/log/rspamd-feedback/worker.log&lt;br /&gt;
exim&amp;amp;#32;-bpc&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Просмотр одного события, подставив его фактический ID из list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
rspamd-feedback&amp;amp;#32;show&amp;amp;#32;ID&amp;amp;#32;--state&amp;amp;#32;processed&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 15.9. Ограничения, очередь и ошибки ===&lt;br /&gt;
Очередь pending ограничена 1024 событиями и 512 MiB; одно письмо — не более 50 MiB. Сборщик сохраняет резерв свободного места 1 GiB плюс максимальное письмо и метаданные. Архив processed содержит root-only снимок и сохранённый исходный event; архив сам не очищается. После 1024 записей выдаётся предупреждение, при 512 MiB worker приостанавливает дальнейшую обработку до обслуживания архива. Нельзя без разбора удалять журнал processed: он нужен для подавления повторов и сохранения неопределённых результатов.&lt;br /&gt;
&lt;br /&gt;
При controller_unavailable проверяются служба Rspamd, loopback 11334, файл controller.password и права. Пока контроллер заведомо недоступен, события остаются pending и могут быть обработаны после восстановления. Status learn_uncertain означает, что запрос уже мог изменить Bayes: автоматического повтора нет; последующие события того же содержимого изолируются для ручного разбора. Не переименовывать такие события обратно в pending и не запускать learn повторно вслепую.&lt;br /&gt;
&lt;br /&gt;
Обратная связь не подтверждает, что класс письма объективно верен: это пользовательский сигнал общей статистической базе. Не выставлять контроллер Rspamd или Redis в интернет и не передавать пароль контроллера Dovecot, PHP либо vmail.&lt;br /&gt;
&lt;br /&gt;
=== 15.10. Отключение без удаления почты и статистики ===&lt;br /&gt;
Остановить только автоматическое обучение:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service&amp;amp;#32;rspamd_feedback&amp;amp;#32;stop&lt;br /&gt;
sysrc&amp;amp;#32;rspamd_feedback_enable=NO&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Оценка Rspamd, доставка и IMAPSieve-сбор событий продолжат работать, уже обученная Bayes-база сохраняется. Пока worker выключен, следить за размером pending.&lt;br /&gt;
&lt;br /&gt;
Чтобы отключить только автоматическую сортировку, убрать из основного dovecot.conf строку !include /usr/local/etc/dovecot/rspamd-spam-delivery.conf, выполнить doveconf -n и service dovecot restart. Обучение по пользовательским перемещениям сохранится.&lt;br /&gt;
&lt;br /&gt;
Для полного отключения обратной связи остановить worker и убрать include rspamd-feedback.conf из dovecot.conf, проверить конфигурацию и перезапустить Dovecot. Для отключения оценки убрать только добавленный блок Rspamd из acl_check_data и spamd_address, проверить exim -bV и перечитать Exim. Если оставляется правило LMTP-сортировки, очистку чужих X-Spam-заголовков необходимо сохранить; проще сначала отключить сортировку. Правила ClamAV, пользовательский каталог и Maildir не удалять. Redis/Rspamd останавливать лишь после исключения зависимых функций.&lt;br /&gt;
&lt;br /&gt;
=== Источники к настройке Rspamd ===&lt;br /&gt;
* [https://docs.rspamd.com/getting-started/first-setup/ Rspamd: Redis, контроллер, secure_ip и первоначальные настройки]&lt;br /&gt;
* [https://docs.rspamd.com/administration/rspamadm/cryptography/ Rspamd: генерация и проверка хеша пароля]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve Dovecot 2.4: IMAPSieve и imapsieve_from]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: Rspamd и defer_ok в ACL]&lt;br /&gt;
&lt;br /&gt;
== Первичные источники ==&lt;br /&gt;
* [https://docs.freebsd.org/en/books/handbook/disks/ FreeBSD Handbook: диски и UFS]&lt;br /&gt;
* [https://man.freebsd.org/cgi/man.cgi?query=gpart&amp;amp;sektion=8 gpart(8)]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_dovecot_authenticator.html Exim: Dovecot authenticator]&lt;br /&gt;
* [https://www.exim.org/exim-html-current/doc/html/spec_html/ch-content_scanning_at_acl_time.html Exim: антивирус в ACL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/howto/sasl/exim.html Dovecot 2.4: Exim SASL]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/auth/databases/passwd_file.html Dovecot 2.4: passwd-file]&lt;br /&gt;
* [https://doc.dovecot.org/2.4.5/core/config/delivery/lmtp.html Dovecot: LMTP]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode acme.sh: ручной DNS-01]&lt;br /&gt;
* [https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc acme.sh: install-cert и hook]&lt;br /&gt;
* [https://github.com/the-djmaze/snappymail/wiki/Admin-Manual SnappyMail: администрирование]&lt;br /&gt;
* [https://nginx.org/en/docs/http/ngx_http_proxy_module.html Nginx: reverse proxy и проверка upstream TLS]&lt;br /&gt;
* [https://docs.clamav.net/manual/Usage/Configuration.html ClamAV: конфигурация]&lt;br /&gt;
* [https://docs.clamav.net/faq/faq-freshclam.html FreshClam: ошибки и ограничения CDN]&lt;br /&gt;
* [https://docs.clamav.net/appendix/CvdPrivateMirror.html ClamAV: зеркала баз]&lt;br /&gt;
* [https://www.eicar.org/download-anti-malware-testfile/ EICAR: стандартный антивирусный тест]&lt;br /&gt;
* [https://grafana.com/docs/alloy/latest/ Grafana Alloy: документация]&lt;br /&gt;
&lt;br /&gt;
[[Категория:FreeBSD]]&lt;br /&gt;
[[Категория:Exim]]&lt;br /&gt;
[[Категория:Mail]]&lt;br /&gt;
[[Категория:DKIM]]&lt;br /&gt;
[[Категория:TLS]]&lt;br /&gt;
[[Категория:Nginx]]&lt;br /&gt;
[[Категория:PHP]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0&amp;diff=665</id>
		<title>Заглавная страница</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0&amp;diff=665"/>
		<updated>2026-10-29T01:30:16Z</updated>

		<summary type="html">&lt;p&gt;Bas: Добавлена ссылка на пошаговую установку Exim и Dovecot на FreeBSD 15.1&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Реклама:&lt;br /&gt;
* Если у Вас есть вопросы или Вам необходима настройка и сопровождение вашего сервера и/или сервисов на базе Linux или FreeBSD, то Вы можете связаться со мной по адресу bas@gwtools.ru&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Список категорий:&lt;br /&gt;
* [[Служебная:Категории|Категории]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Список статей:&lt;br /&gt;
* [[Автодополнение в командной строке]]&lt;br /&gt;
* [[Базовая настройка FreeBSD]]&lt;br /&gt;
* [[Выпуск SSL-сертификата]]&lt;br /&gt;
* [[Вытаскивание файлов из кеша Squid&#039;а]]&lt;br /&gt;
* [[Давай я за тебя погуглю]]&lt;br /&gt;
* [[Добавление пользователя для удалённого доступа в MySQL]]&lt;br /&gt;
* [[Добавление местонахождения в vBulletin на странице online.php]]&lt;br /&gt;
* [[Изменение главного меню в Zabbix]]&lt;br /&gt;
* [[Кеширование компиляции с помощью ccache]]&lt;br /&gt;
* [[Монтирование glusterfs-разделов при загрузке системы]]&lt;br /&gt;
* [[Монтирование ISO-образа]]&lt;br /&gt;
* [[На Toshiba Sattelite перестали работать сенсорные кнопки]]&lt;br /&gt;
* [[Настройка OpenVPN на FreeBSD]]&lt;br /&gt;
* [[Настройка внешней авторизации в Apache]]&lt;br /&gt;
* [[Настройка мониторинга MySQL с помощью Zabbix]]&lt;br /&gt;
* [[Настройка ограничений на отправку в Exim (ratelimit)]]&lt;br /&gt;
* [[Обновление часового пояса на FreeBSD]]&lt;br /&gt;
* [[Ограничение доступа к Squid (по адресу, по паролю, по времени)]]&lt;br /&gt;
* [[Отказ от SSLv2 и SSLv3 с уведомлением пользователей]]&lt;br /&gt;
* [[Отключение правил форума при регистрации]]&lt;br /&gt;
* [[Отключение формы обратной связи‎‎]]&lt;br /&gt;
* [[Отправка файла по почте через консоль]]&lt;br /&gt;
* [[Поиск во FreeBSD]]&lt;br /&gt;
* [[Поиск установленных соединений]]&lt;br /&gt;
* [[Почтовый сервер на FreeBSD 15.1: Exim и Dovecot]]&lt;br /&gt;
* [[Правильный перезапуск сети и маршрутизации во FreeBSD]]&lt;br /&gt;
* [[Проверка часового пояса]]&lt;br /&gt;
* [[Репликация MySQL]]&lt;br /&gt;
* [[Создание раздела более 2 ТБ]]&lt;br /&gt;
* [[Создание раздела с помощью parted]]&lt;br /&gt;
* [[Тестирование скорости чтения и записи жёсткого диска]]&lt;br /&gt;
* [[Удаление ^M]]&lt;br /&gt;
* [[Удаление Powered by vBulletin из заголовка страницы]]&lt;br /&gt;
* [[Управление пользователями с помощью bash-скриптов]]&lt;br /&gt;
* [[Управление разделами с помощью gpart]]&lt;br /&gt;
* [[Установка Bareos на FreeBSD]]&lt;br /&gt;
* [[Установка и настройка git на FreeBSD (freebsd+nginx+fcgiwrap+git+gitweb)]]&lt;br /&gt;
* [[Установка zabbix-agent на SUSE Linux Enterprise Server]]&lt;br /&gt;
* [[error_reporting и вывод на экран]]&lt;br /&gt;
* [[.htaccess и mod rewrite в nginx или как я мигрировал с Apache]]&lt;br /&gt;
* [[Модули для Apache - мой мини-комплект]]&lt;br /&gt;
* [[Apache: Couldn&#039;t create accept lock]]&lt;br /&gt;
* [[Out of inodes]]&lt;br /&gt;
* [[RHEL обновление лицензии]]&lt;br /&gt;
* [[Toshiba, обновление BIOS, проблемы с яркостью экрана, регулятором громкости]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0&amp;diff=663</id>
		<title>Заглавная страница</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0&amp;diff=663"/>
		<updated>2024-08-09T23:00:25Z</updated>

		<summary type="html">&lt;p&gt;Bas: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Реклама:&lt;br /&gt;
* Если у Вас есть вопросы или Вам необходима настройка и сопровождение вашего сервера и/или сервисов на базе Linux или FreeBSD, то Вы можете связаться со мной по адресу bas@gwtools.ru&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Список категорий:&lt;br /&gt;
* [[Служебная:Категории|Категории]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Список статей:&lt;br /&gt;
* [[Автодополнение в командной строке]]&lt;br /&gt;
* [[Базовая настройка FreeBSD]]&lt;br /&gt;
* [[Выпуск SSL-сертификата]]&lt;br /&gt;
* [[Вытаскивание файлов из кеша Squid&#039;а]]&lt;br /&gt;
* [[Давай я за тебя погуглю]]&lt;br /&gt;
* [[Добавление пользователя для удалённого доступа в MySQL]]&lt;br /&gt;
* [[Добавление местонахождения в vBulletin на странице online.php]]&lt;br /&gt;
* [[Изменение главного меню в Zabbix]]&lt;br /&gt;
* [[Кеширование компиляции с помощью ccache]]&lt;br /&gt;
* [[Монтирование glusterfs-разделов при загрузке системы]]&lt;br /&gt;
* [[Монтирование ISO-образа]]&lt;br /&gt;
* [[На Toshiba Sattelite перестали работать сенсорные кнопки]]&lt;br /&gt;
* [[Настройка OpenVPN на FreeBSD]]&lt;br /&gt;
* [[Настройка внешней авторизации в Apache]]&lt;br /&gt;
* [[Настройка мониторинга MySQL с помощью Zabbix]]&lt;br /&gt;
* [[Настройка ограничений на отправку в Exim (ratelimit)]]&lt;br /&gt;
* [[Обновление часового пояса на FreeBSD]]&lt;br /&gt;
* [[Ограничение доступа к Squid (по адресу, по паролю, по времени)]]&lt;br /&gt;
* [[Отказ от SSLv2 и SSLv3 с уведомлением пользователей]]&lt;br /&gt;
* [[Отключение правил форума при регистрации]]&lt;br /&gt;
* [[Отключение формы обратной связи‎‎]]&lt;br /&gt;
* [[Отправка файла по почте через консоль]]&lt;br /&gt;
* [[Поиск во FreeBSD]]&lt;br /&gt;
* [[Поиск установленных соединений]]&lt;br /&gt;
* [[Правильный перезапуск сети и маршрутизации во FreeBSD]]&lt;br /&gt;
* [[Проверка часового пояса]]&lt;br /&gt;
* [[Репликация MySQL]]&lt;br /&gt;
* [[Создание раздела более 2 ТБ]]&lt;br /&gt;
* [[Создание раздела с помощью parted]]&lt;br /&gt;
* [[Тестирование скорости чтения и записи жёсткого диска]]&lt;br /&gt;
* [[Удаление ^M]]&lt;br /&gt;
* [[Удаление Powered by vBulletin из заголовка страницы]]&lt;br /&gt;
* [[Управление пользователями с помощью bash-скриптов]]&lt;br /&gt;
* [[Управление разделами с помощью gpart]]&lt;br /&gt;
* [[Установка Bareos на FreeBSD]]&lt;br /&gt;
* [[Установка и настройка git на FreeBSD (freebsd+nginx+fcgiwrap+git+gitweb)]]&lt;br /&gt;
* [[Установка zabbix-agent на SUSE Linux Enterprise Server]]&lt;br /&gt;
* [[error_reporting и вывод на экран]]&lt;br /&gt;
* [[.htaccess и mod rewrite в nginx или как я мигрировал с Apache]]&lt;br /&gt;
* [[Модули для Apache - мой мини-комплект]]&lt;br /&gt;
* [[Apache: Couldn&#039;t create accept lock]]&lt;br /&gt;
* [[Out of inodes]]&lt;br /&gt;
* [[RHEL обновление лицензии]]&lt;br /&gt;
* [[Toshiba, обновление BIOS, проблемы с яркостью экрана, регулятором громкости]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=RHEL_%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BB%D0%B8%D1%86%D0%B5%D0%BD%D0%B7%D0%B8%D0%B8&amp;diff=662</id>
		<title>RHEL обновление лицензии</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=RHEL_%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BB%D0%B8%D1%86%D0%B5%D0%BD%D0%B7%D0%B8%D0%B8&amp;diff=662"/>
		<updated>2024-08-09T22:59:54Z</updated>

		<summary type="html">&lt;p&gt;Bas: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Если возникают какие-то проблемы с обновлением системы или пакетов, наподобие следующей:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# dnf check-update&lt;br /&gt;
Updating Subscription Management repositories.&lt;br /&gt;
&lt;br /&gt;
This system is registered with an entitlement server, but is not receiving updates. You can use subscription-manager to assign subscriptions.&lt;br /&gt;
&lt;br /&gt;
*** WARNING ***&lt;br /&gt;
The subscription for following product(s) has expired:&lt;br /&gt;
...&lt;br /&gt;
You no longer have access to the repositories that provide these products.  It is important that you apply an active subscription in order to resume access to security and other critical updates. If you don&#039;t have other active subscriptions, you can renew the expired subscription.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Зайдите и проверьте наличие подписки на сайт [https://www.redhat.com Red Hat]&lt;br /&gt;
Необходимо выполнить следующие команды:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# subscription-manager remove --all&lt;br /&gt;
# subscription-manager clean&lt;br /&gt;
# subscription-manager register&lt;br /&gt;
# subscription-manager attach --auto&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Информация взята из базы знаний: https://access.redhat.com/solutions/4078831&lt;br /&gt;
[[Category:Linux]]&lt;br /&gt;
[[Category:RHEL]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=RHEL_%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BB%D0%B8%D1%86%D0%B5%D0%BD%D0%B7%D0%B8%D0%B8&amp;diff=661</id>
		<title>RHEL обновление лицензии</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=RHEL_%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BB%D0%B8%D1%86%D0%B5%D0%BD%D0%B7%D0%B8%D0%B8&amp;diff=661"/>
		<updated>2024-08-09T22:56:27Z</updated>

		<summary type="html">&lt;p&gt;Bas: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Если возникают какие-то проблемы с обновлением системы или пакетов, наподобие следующей:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# dnf check-update&lt;br /&gt;
Updating Subscription Management repositories.&lt;br /&gt;
&lt;br /&gt;
This system is registered with an entitlement server, but is not receiving updates. You can use subscription-manager to assign subscriptions.&lt;br /&gt;
&lt;br /&gt;
*** WARNING ***&lt;br /&gt;
The subscription for following product(s) has expired:&lt;br /&gt;
...&lt;br /&gt;
You no longer have access to the repositories that provide these products.  It is important that you apply an active subscription in order to resume access to security and other critical updates. If you don&#039;t have other active subscriptions, you can renew the expired subscription.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Необходимо выполнить следующие команды:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# subscription-manager remove --all&lt;br /&gt;
# subscription-manager clean&lt;br /&gt;
# subscription-manager register&lt;br /&gt;
# subscription-manager attach --auto&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
https://access.redhat.com/solutions/4078831&lt;br /&gt;
[[Category:Linux]]&lt;br /&gt;
[[Category:RHEL]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=RHEL_%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BB%D0%B8%D1%86%D0%B5%D0%BD%D0%B7%D0%B8%D0%B8&amp;diff=660</id>
		<title>RHEL обновление лицензии</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=RHEL_%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BB%D0%B8%D1%86%D0%B5%D0%BD%D0%B7%D0%B8%D0%B8&amp;diff=660"/>
		<updated>2024-08-09T22:55:51Z</updated>

		<summary type="html">&lt;p&gt;Bas: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Если возникают какие-то проблемы с обновлением системы или пакетов, наподобие следующей:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# dnf check-update&lt;br /&gt;
Updating Subscription Management repositories.&lt;br /&gt;
&lt;br /&gt;
This system is registered with an entitlement server, but is not receiving updates. You can use subscription-manager to assign subscriptions.&lt;br /&gt;
&lt;br /&gt;
*** WARNING ***&lt;br /&gt;
The subscription for following product(s) has expired:&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Необходимо выполнить следующие команды:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# subscription-manager remove --all&lt;br /&gt;
# subscription-manager clean&lt;br /&gt;
# subscription-manager register&lt;br /&gt;
# subscription-manager attach --auto&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
https://access.redhat.com/solutions/4078831&lt;br /&gt;
[[Category:Linux]]&lt;br /&gt;
[[Category:RHEL]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=RHEL_%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BB%D0%B8%D1%86%D0%B5%D0%BD%D0%B7%D0%B8%D0%B8&amp;diff=659</id>
		<title>RHEL обновление лицензии</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=RHEL_%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BB%D0%B8%D1%86%D0%B5%D0%BD%D0%B7%D0%B8%D0%B8&amp;diff=659"/>
		<updated>2024-08-09T22:53:53Z</updated>

		<summary type="html">&lt;p&gt;Bas: Новая страница: «Если возникают какие-то проблемы с обновлением системы или пакетов, то необходимо выполнить следующие команды:  &amp;lt;pre&amp;gt; # subscription-manager remove --all # subscription-manager clean # subscription-manager register # subscription-manager attach --auto &amp;lt;/pre&amp;gt;  https://access.redhat.com/solutions/4078831 Category:Linux Category:RHEL»&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Если возникают какие-то проблемы с обновлением системы или пакетов, то необходимо выполнить следующие команды:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# subscription-manager remove --all&lt;br /&gt;
# subscription-manager clean&lt;br /&gt;
# subscription-manager register&lt;br /&gt;
# subscription-manager attach --auto&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
https://access.redhat.com/solutions/4078831&lt;br /&gt;
[[Category:Linux]]&lt;br /&gt;
[[Category:RHEL]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0&amp;diff=658</id>
		<title>Заглавная страница</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0&amp;diff=658"/>
		<updated>2024-07-26T22:22:09Z</updated>

		<summary type="html">&lt;p&gt;Bas: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Реклама:&lt;br /&gt;
* Если у Вас есть вопросы или Вам необходима настройка и сопровождение вашего сервера и/или сервисов на базе Linux или FreeBSD, то Вы можете связаться со мной по адресу bas@gwtools.ru&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Список категорий:&lt;br /&gt;
* [[Служебная:Категории|Категории]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Список статей:&lt;br /&gt;
* [[Автодополнение в командной строке]]&lt;br /&gt;
* [[Базовая настройка FreeBSD]]&lt;br /&gt;
* [[Выпуск SSL-сертификата]]&lt;br /&gt;
* [[Вытаскивание файлов из кеша Squid&#039;а]]&lt;br /&gt;
* [[Давай я за тебя погуглю]]&lt;br /&gt;
* [[Добавление пользователя для удалённого доступа в MySQL]]&lt;br /&gt;
* [[Добавление местонахождения в vBulletin на странице online.php]]&lt;br /&gt;
* [[Изменение главного меню в Zabbix]]&lt;br /&gt;
* [[Кеширование компиляции с помощью ccache]]&lt;br /&gt;
* [[Монтирование glusterfs-разделов при загрузке системы]]&lt;br /&gt;
* [[Монтирование ISO-образа]]&lt;br /&gt;
* [[На Toshiba Sattelite перестали работать сенсорные кнопки]]&lt;br /&gt;
* [[Настройка OpenVPN на FreeBSD]]&lt;br /&gt;
* [[Настройка внешней авторизации в Apache]]&lt;br /&gt;
* [[Настройка мониторинга MySQL с помощью Zabbix]]&lt;br /&gt;
* [[Настройка ограничений на отправку в Exim (ratelimit)]]&lt;br /&gt;
* [[Обновление часового пояса на FreeBSD]]&lt;br /&gt;
* [[Ограничение доступа к Squid (по адресу, по паролю, по времени)]]&lt;br /&gt;
* [[Отказ от SSLv2 и SSLv3 с уведомлением пользователей]]&lt;br /&gt;
* [[Отключение правил форума при регистрации]]&lt;br /&gt;
* [[Отключение формы обратной связи‎‎]]&lt;br /&gt;
* [[Отправка файла по почте через консоль]]&lt;br /&gt;
* [[Поиск во FreeBSD]]&lt;br /&gt;
* [[Поиск установленных соединений]]&lt;br /&gt;
* [[Правильный перезапуск сети и маршрутизации во FreeBSD]]&lt;br /&gt;
* [[Проверка часового пояса]]&lt;br /&gt;
* [[Репликация MySQL]]&lt;br /&gt;
* [[Создание раздела более 2 ТБ]]&lt;br /&gt;
* [[Создание раздела с помощью parted]]&lt;br /&gt;
* [[Тестирование скорости чтения и записи жёсткого диска]]&lt;br /&gt;
* [[Удаление ^M]]&lt;br /&gt;
* [[Удаление Powered by vBulletin из заголовка страницы]]&lt;br /&gt;
* [[Управление пользователями с помощью bash-скриптов]]&lt;br /&gt;
* [[Управление разделами с помощью gpart]]&lt;br /&gt;
* [[Установка Bareos на FreeBSD]]&lt;br /&gt;
* [[Установка и настройка git на FreeBSD (freebsd+nginx+fcgiwrap+git+gitweb)]]&lt;br /&gt;
* [[Установка zabbix-agent на SUSE Linux Enterprise Server]]&lt;br /&gt;
* [[error_reporting и вывод на экран]]&lt;br /&gt;
* [[.htaccess и mod rewrite в nginx или как я мигрировал с Apache]]&lt;br /&gt;
* [[Модули для Apache - мой мини-комплект]]&lt;br /&gt;
* [[Apache: Couldn&#039;t create accept lock]]&lt;br /&gt;
* [[Out of inodes]]&lt;br /&gt;
* [[Toshiba, обновление BIOS, проблемы с яркостью экрана, регулятором громкости]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0&amp;diff=657</id>
		<title>Заглавная страница</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0&amp;diff=657"/>
		<updated>2024-07-26T22:20:54Z</updated>

		<summary type="html">&lt;p&gt;Bas: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Реклама:&lt;br /&gt;
* Если у Вас есть вопросы или Вам необходима настройка и сопровождение вашего сервера и/или сервисов на базе Linux или FreeBSD, то Вы можете связаться со мной по адресу bas@gwtools.ru&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Список категорий:&lt;br /&gt;
* [[Служебная:Категории|Категории]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Список статей:&lt;br /&gt;
* [[Автодополнение в командной строке]]&lt;br /&gt;
* [[Базовая настройка FreeBSD]]&lt;br /&gt;
* [[Выпуск SSL-сертификата]]&lt;br /&gt;
* [[Вытаскивание файлов из кеша Squid&#039;а]]&lt;br /&gt;
* [[Изменение главного меню в Zabbix]]&lt;br /&gt;
* [[Настройка OpenVPN на FreeBSD]]&lt;br /&gt;
* [[Настройка внешней авторизации в Apache]]&lt;br /&gt;
* [[Настройка мониторинга MySQL с помощью Zabbix]]&lt;br /&gt;
* [[Давай я за тебя погуглю]]&lt;br /&gt;
* [[Добавление пользователя для удалённого доступа в MySQL]]&lt;br /&gt;
* [[Добавление местонахождения в vBulletin на странице online.php]]&lt;br /&gt;
* [[Кеширование компиляции с помощью ccache]]&lt;br /&gt;
* [[Монтирование glusterfs-разделов при загрузке системы]]&lt;br /&gt;
* [[Монтирование ISO-образа]]&lt;br /&gt;
* [[На Toshiba Sattelite перестали работать сенсорные кнопки]]&lt;br /&gt;
* [[Настройка ограничений на отправку в Exim (ratelimit)]]&lt;br /&gt;
* [[Обновление часового пояса на FreeBSD]]&lt;br /&gt;
* [[Ограничение доступа к Squid (по адресу, по паролю, по времени)]]&lt;br /&gt;
* [[Отказ от SSLv2 и SSLv3 с уведомлением пользователей]]&lt;br /&gt;
* [[Отключение правил форума при регистрации]]&lt;br /&gt;
* [[Отключение формы обратной связи‎‎]]&lt;br /&gt;
* [[Отправка файла по почте через консоль]]&lt;br /&gt;
* [[Поиск во FreeBSD]]&lt;br /&gt;
* [[Поиск установленных соединений]]&lt;br /&gt;
* [[Правильный перезапуск сети и маршрутизации во FreeBSD]]&lt;br /&gt;
* [[Проверка часового пояса]]&lt;br /&gt;
* [[Репликация MySQL]]&lt;br /&gt;
* [[Создание раздела более 2 ТБ]]&lt;br /&gt;
* [[Создание раздела с помощью parted]]&lt;br /&gt;
* [[Тестирование скорости чтения и записи жёсткого диска]]&lt;br /&gt;
* [[Удаление ^M]]&lt;br /&gt;
* [[Удаление Powered by vBulletin из заголовка страницы]]&lt;br /&gt;
* [[Управление пользователями с помощью bash-скриптов]]&lt;br /&gt;
* [[Управление разделами с помощью gpart]]&lt;br /&gt;
* [[Установка Bareos на FreeBSD]]&lt;br /&gt;
* [[Установка и настройка git на FreeBSD (freebsd+nginx+fcgiwrap+git+gitweb)]]&lt;br /&gt;
* [[Установка zabbix-agent на SUSE Linux Enterprise Server]]&lt;br /&gt;
* [[error_reporting и вывод на экран]]&lt;br /&gt;
* [[.htaccess и mod rewrite в nginx или как я мигрировал с Apache]]&lt;br /&gt;
* [[Модули для Apache - мой мини-комплект]]&lt;br /&gt;
* [[Apache: Couldn&#039;t create accept lock]]&lt;br /&gt;
* [[Out of inodes]]&lt;br /&gt;
* [[Toshiba, обновление BIOS, проблемы с яркостью экрана, регулятором громкости]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=VMWare_ESXi_%D0%A0%D0%B0%D0%B1%D0%BE%D1%82%D0%B0_%D1%81_%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D0%B0%D0%BC%D0%B8&amp;diff=656</id>
		<title>VMWare ESXi Работа с разделами</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=VMWare_ESXi_%D0%A0%D0%B0%D0%B1%D0%BE%D1%82%D0%B0_%D1%81_%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D0%B0%D0%BC%D0%B8&amp;diff=656"/>
		<updated>2024-01-16T06:24:25Z</updated>

		<summary type="html">&lt;p&gt;Bas: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Список дисков:&lt;br /&gt;
esxcfg-scsidevs --list&lt;br /&gt;
esxcfg-scsidevs -c&lt;br /&gt;
&lt;br /&gt;
Список datastores:&lt;br /&gt;
esxcli storage vmfs extent list&lt;br /&gt;
&lt;br /&gt;
Список разделов:&lt;br /&gt;
partedUtil get /путь_до_диска/имя_диска&lt;br /&gt;
partedUtil get &amp;quot;/vmfs/devices/disks/naa.600508b1001c1e6f934a3bfd9f641fb3&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Удалить раздел:&lt;br /&gt;
partedUtil delete /путь_до_диска/имя_диска номер_раздела&lt;br /&gt;
&lt;br /&gt;
partedUtil delete /vmfs/devices/disks/naa.600508b1001c24a60dc3f79980dc4948 92&lt;br /&gt;
partedUtil delete /vmfs/devices/disks/naa.600508b1001c7ab9fdfec6be437b978c 4&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Изменить раздел:&lt;br /&gt;
partedUtil  resize /путь_до_диска/имя_диска номер_раздела начальный_сектор конечный_сектор&lt;br /&gt;
&lt;br /&gt;
Создание раздела:&lt;br /&gt;
partedUtil setptbl /путь_до_диска/имя_диска имя_раздела номер_раздела начальный_сектор конечный_сектор тип\guid&lt;br /&gt;
&lt;br /&gt;
partedUtil setptbl &amp;quot;/vmfs/devices/disks/naa.600508b1001c24a60dc3f79980dc4948&amp;quot; gpt &amp;quot;4 2566916099 2671773699 AA31E02A400F11DB9590000C2911D1B8 0&amp;quot;&lt;br /&gt;
partedUtil setptbl &amp;quot;/vmfs/devices/disks/naa.600508b1001c24a60dc3f79980dc4948&amp;quot; gpt &amp;quot;5 2671773700 2776631300 AA31E02A400F11DB9590000C2911D1B8 0&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
partedUtil setptbl &amp;quot;/vmfs/devices/disks/naa.600508b1001c1e6f934a3bfd9f641fb3&amp;quot; gpt &amp;quot;1 2048 104859648 AA31E02A400F11DB9590000C2911D1B8 0&amp;quot; &amp;quot;2 104859649 209717249 AA31E02A400F11DB9590000C2911D1B8 0&amp;quot; &amp;quot;3 209717250 419432450 AA31E02A400F11DB9590000C2911D1B8 0&amp;quot; &amp;quot;4 419432451 629147651 AA31E02A400F11DB9590000C2911D1B8 0&amp;quot; &amp;quot;5 629147652 1953457584 AA31E02A400F11DB9590000C2911D1B8 0&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
  1 TB 2147483648&lt;br /&gt;
100 GB 209715200&lt;br /&gt;
 50 GB 104857600&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
vmkfstools -C vmfs5 -S sata.50.1 /vmfs/devices/disks/naa.600508b1001c1e6f934a3bfd9f641fb3:1&lt;br /&gt;
vmkfstools -C vmfs5 -S sata.50.2 /vmfs/devices/disks/naa.600508b1001c1e6f934a3bfd9f641fb3:2&lt;br /&gt;
vmkfstools -C vmfs5 -S sata.100.1 /vmfs/devices/disks/naa.600508b1001c1e6f934a3bfd9f641fb3:3&lt;br /&gt;
vmkfstools -C vmfs5 -S sata.100.2 /vmfs/devices/disks/naa.600508b1001c1e6f934a3bfd9f641fb3:4&lt;br /&gt;
vmkfstools -C vmfs5 -S sata /vmfs/devices/disks/naa.600508b1001c1e6f934a3bfd9f641fb3:5&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Пометить диск как SSD:&lt;br /&gt;
esxcli storage nmp satp rule add -satp VMW_SATP_LOCAL --device naa.600508b1001c7ab9fdfec6be437b978c --option=&amp;quot;enable_local enable_ssd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
[[Category:VMWare]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=655</id>
		<title>Настройка Exim на FreeBSD</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=655"/>
		<updated>2023-07-01T17:02:42Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* DKIM */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Дано: FreeBSD 13, Exim 4.&lt;br /&gt;
Задача: развернуть почтовый relay, с поддержкой шифрования.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Исторически во FreeBSD используется sendmail. Он отлично выполняет свои функции, но в современном мире сложен в настройке, т.к. конфиг представляет собой набор сложночитаемых макросов.&lt;br /&gt;
&lt;br /&gt;
===== Установка exim =====&lt;br /&gt;
Воспользуемся пакетным менеджером:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg install exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После установки будет показана страница с дальнейшими шагами, которые надо выполнить.&amp;lt;br&amp;gt;&lt;br /&gt;
Если вы закрыли консоль, то найти эту информацию можно в файле &amp;lt;code&amp;gt;/usr/local/share/doc/exim/POST-INSTALL-NOTES&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== mailer.conf =====&lt;br /&gt;
Необходимо поменять sendmail на exim в файле &amp;lt;code&amp;gt;/etc/mail/mailer.conf&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Информацию для данного файла можно взять с оф. документации в пункте [https://www.exim.org/exim-html-current/doc/html/spec%20html/ch-building%20and%20installing%20exim.html 20. Replacing another MTA with Exim]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/exim/bin/exim&lt;br /&gt;
send-mail	/usr/exim/bin/exim&lt;br /&gt;
mailq		/usr/exim/bin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так же эту информацию можно взять из &amp;lt;code&amp;gt;man 5 mailer.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате я сделал общие изменения в таком виде:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
send-mail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение sendmail =====&lt;br /&gt;
В &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; добавляем строку &amp;lt;code&amp;gt;sendmail_enable=&amp;quot;NONE&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение заданий из расписания =====&lt;br /&gt;
Теперь нужно выключить скрипты, которые выполняются по расписанию.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; По умолчанию файла &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; в системе нет.&amp;lt;br&amp;gt;&lt;br /&gt;
Создаём его и добавляем следующее содержимое (всё из той же информации после установки):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
daily_clean_hoststat_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_status_include_submit_mailq=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_submit_queuerun=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_queuerun_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Включение exim =====&lt;br /&gt;
После добавления этой опции и редактирования конфига мы сможем запустить Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;exim_enable=&amp;quot;YES&amp;quot;&#039; &amp;gt; /etc/rc.conf.d/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Ротация логов =====&lt;br /&gt;
Создаём файл &amp;lt;code&amp;gt;/etc/newsyslog.conf.d/exim.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog   mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
/var/log/exim/rejectlog mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Настройка конфига =====&lt;br /&gt;
Открываем &amp;lt;code&amp;gt;/usr/local/etc/exim/configure&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для старта необходимо не так уж и много правок.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# primary_hostname = &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
По умолчанию опция закомментирована и информация берется из системы, но я бы рекомендовал активировать эту опцию.&amp;lt;br&amp;gt;&lt;br /&gt;
Указываем адрес сервера полностью, с доменом. Если сервер работает и на локальную сеть, и на интернет, то указываем существующий и доступный домен для интернета:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
primary_hostname = mx1.gwtools.ru&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = @&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Указываем адреса и домены с доступом к почте. Разделителем является двоеточие.&amp;lt;br&amp;gt;&lt;br /&gt;
Список local_domains содержит список обслуживаемых этим сервером доменов.&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_to_domains содержит список доменов, на которые можно отправлять почту без авторизации (проверка по получателю, например, на др. почтовый сервер).&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_from_hosts содержит список адресов или доменов, с которых разрешён доступ на отправку почты через данный сервер (например, локальная сеть).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = gwtools.ru&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost : 192.168.0.0/24&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Запуск exim =====&lt;br /&gt;
Данных изменений достаточно, чтобы запустить Exim и он мог получать и отправлять почту:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service exim start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DKIM ====&lt;br /&gt;
Переходим в папке с конфигами Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd /usr/local/etc/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Приватный ключ =====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl genrsa -out gwtools.ru.private 1024&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Публичный ключ =====&lt;br /&gt;
Публичный ключ генерируется на основе приватного и добавляется в DNS-запись и поэтому потребуется нам немного позже.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl rsa -in gwtools.ru.private -pubout &amp;gt; gwtools.ru.public&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DNS ====&lt;br /&gt;
===== MX =====&lt;br /&gt;
Для того, чтобы сервера знали, куда отправлять почту для домена, нужно добавить MX-запись.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; Я рекомендую добавлять запись на почтовый сервер именно как A-запись, а не CNAME (так же, как и для NS-записей):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    MX    10 mx1.gwtools.ru&lt;br /&gt;
mx1           A     1.2.3.4&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== SPF =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о том, с какого сервера будет отправляться почта и что делать с почтой с других адресов.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее о синтаксисе SPF можно прочитать на [https://yandex.ru/support/business/domains/dns/spf.html Яндексе] или [https://support.google.com/a/answer/10683907?hl=ru Google]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    TXT    &amp;quot;v=spf1 ip4:1.2.3.4 ~all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DKIM =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о публичном ключе, который мы сгенерировали ранее:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mx1    TXT    &amp;quot;v=DKIM1; k=rsa; t=s; p=ABC...XYZ&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== ADSP =====&lt;br /&gt;
ADSP (Author Domain Signing Practices, http://tools.ietf.org/search/rfc5617 ) — практики использования DKIM в домене.&amp;lt;br&amp;gt;&lt;br /&gt;
ADSP это опциональное расширение DKIM, которое позволяет сообщать почтовым серверам-получателям, что делать с письмом пришедшим с якобы нашего домена, но не имеющее подписи.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_adsp    TXT    &amp;quot;dkim=all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DMARC =====&lt;br /&gt;
Если кратко, то это следующий уровень политик по обработке почты, который базируется на SPF и DKIM. В зависимости от результатов проверки письмо можно пропустить, отправить в карантин или отклонить.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее можно прочитать на оф. сайте https://dmarc.org/overview/ или в [https://support.google.com/a/answer/2466563?hl=ru Google].&amp;lt;br&amp;gt;&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о правилах DMARC:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_dmarc    TXT    &amp;quot;v=DMARC1; p=reject; pct=100; sp=reject; aspf=s; adkim=s; fo=1; ri=86400&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Использование SSL/TLS шифрования при подключении ====&lt;br /&gt;
===== Получение сертификатов =====&lt;br /&gt;
&lt;br /&gt;
===== Настройка сертификатов =====&lt;br /&gt;
&lt;br /&gt;
==== Отправка тестового письма с консоли ====&lt;br /&gt;
Воспользуемся telnet c почтового сервера:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
telnet localhost 25&lt;br /&gt;
HELO localhost&lt;br /&gt;
MAIL FROM:user@gwtools.ru&lt;br /&gt;
RCPT TO:user@ya.ru&lt;br /&gt;
DATA&lt;br /&gt;
SUBJECT:test subj&lt;br /&gt;
test msg&lt;br /&gt;
.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:FreeBSD]]&lt;br /&gt;
[[Category:Exim]]&lt;br /&gt;
[[Category:Mail]]&lt;br /&gt;
[[Category:SPF]]&lt;br /&gt;
[[Category:DKIM]]&lt;br /&gt;
[[Category:DMARC]]&lt;br /&gt;
[[Category:Let&#039;s Encrypt]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=654</id>
		<title>Настройка Exim на FreeBSD</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=654"/>
		<updated>2023-07-01T17:01:20Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* Публичный ключ */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Дано: FreeBSD 13, Exim 4.&lt;br /&gt;
Задача: развернуть почтовый relay, с поддержкой шифрования.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Исторически во FreeBSD используется sendmail. Он отлично выполняет свои функции, но в современном мире сложен в настройке, т.к. конфиг представляет собой набор сложночитаемых макросов.&lt;br /&gt;
&lt;br /&gt;
===== Установка exim =====&lt;br /&gt;
Воспользуемся пакетным менеджером:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg install exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После установки будет показана страница с дальнейшими шагами, которые надо выполнить.&amp;lt;br&amp;gt;&lt;br /&gt;
Если вы закрыли консоль, то найти эту информацию можно в файле &amp;lt;code&amp;gt;/usr/local/share/doc/exim/POST-INSTALL-NOTES&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== mailer.conf =====&lt;br /&gt;
Необходимо поменять sendmail на exim в файле &amp;lt;code&amp;gt;/etc/mail/mailer.conf&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Информацию для данного файла можно взять с оф. документации в пункте [https://www.exim.org/exim-html-current/doc/html/spec%20html/ch-building%20and%20installing%20exim.html 20. Replacing another MTA with Exim]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/exim/bin/exim&lt;br /&gt;
send-mail	/usr/exim/bin/exim&lt;br /&gt;
mailq		/usr/exim/bin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так же эту информацию можно взять из &amp;lt;code&amp;gt;man 5 mailer.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате я сделал общие изменения в таком виде:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
send-mail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение sendmail =====&lt;br /&gt;
В &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; добавляем строку &amp;lt;code&amp;gt;sendmail_enable=&amp;quot;NONE&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение заданий из расписания =====&lt;br /&gt;
Теперь нужно выключить скрипты, которые выполняются по расписанию.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; По умолчанию файла &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; в системе нет.&amp;lt;br&amp;gt;&lt;br /&gt;
Создаём его и добавляем следующее содержимое (всё из той же информации после установки):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
daily_clean_hoststat_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_status_include_submit_mailq=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_submit_queuerun=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_queuerun_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Включение exim =====&lt;br /&gt;
После добавления этой опции и редактирования конфига мы сможем запустить Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;exim_enable=&amp;quot;YES&amp;quot;&#039; &amp;gt; /etc/rc.conf.d/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Ротация логов =====&lt;br /&gt;
Создаём файл &amp;lt;code&amp;gt;/etc/newsyslog.conf.d/exim.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog   mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
/var/log/exim/rejectlog mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Настройка конфига =====&lt;br /&gt;
Открываем &amp;lt;code&amp;gt;/usr/local/etc/exim/configure&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для старта необходимо не так уж и много правок.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# primary_hostname = &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
По умолчанию опция закомментирована и информация берется из системы, но я бы рекомендовал активировать эту опцию.&amp;lt;br&amp;gt;&lt;br /&gt;
Указываем адрес сервера полностью, с доменом. Если сервер работает и на локальную сеть, и на интернет, то указываем существующий и доступный домен для интернета:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
primary_hostname = mx1.gwtools.ru&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = @&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Указываем адреса и домены с доступом к почте. Разделителем является двоеточие.&amp;lt;br&amp;gt;&lt;br /&gt;
Список local_domains содержит список обслуживаемых этим сервером доменов.&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_to_domains содержит список доменов, на которые можно отправлять почту без авторизации (проверка по получателю, например, на др. почтовый сервер).&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_from_hosts содержит список адресов или доменов, с которых разрешён доступ на отправку почты через данный сервер (например, локальная сеть).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = gwtools.ru&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost : 192.168.0.0/24&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Запуск exim =====&lt;br /&gt;
Данных изменений достаточно, чтобы запустить Exim и он мог получать и отправлять почту:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service exim start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DKIM ====&lt;br /&gt;
Переходим в папке с конфигами Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd /usr/local/etc/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Приватный ключ =====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl genrsa -out gwtools.ru.private 1024&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Публичный ключ =====&lt;br /&gt;
Публичный ключ генерируется на основе приватного и добавляется в DNS-запись и поэтому потребуется нам немного позже.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl rsa -in gwtools.ru.private -pubout &amp;gt; gwtools.ru.public&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DNS ====&lt;br /&gt;
===== MX =====&lt;br /&gt;
Для того, чтобы сервера знали, куда отправлять почту для домена, нужно добавить MX-запись.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; Я рекомендую добавлять запись на почтовый сервер именно как A-запись, а не CNAME (так же, как и для NS-записей):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    MX    10 mx1.gwtools.ru&lt;br /&gt;
mx1           A     1.2.3.4&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== SPF =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о том, с какого сервера будет отправляться почта и что делать с почтой с других адресов.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее о синтаксисе SPF можно прочитать на [https://yandex.ru/support/business/domains/dns/spf.html Яндексе] или [https://support.google.com/a/answer/10683907?hl=ru Google]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    TXT    &amp;quot;v=spf1 ip4:1.2.3.4 ~all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DKIM =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о публичном ключе:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mx1    TXT    &amp;quot;v=DKIM1; k=rsa; t=s; p=ABC...XYZ&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== ADSP =====&lt;br /&gt;
ADSP (Author Domain Signing Practices, http://tools.ietf.org/search/rfc5617 ) — практики использования DKIM в домене.&amp;lt;br&amp;gt;&lt;br /&gt;
ADSP это опциональное расширение DKIM, которое позволяет сообщать почтовым серверам-получателям, что делать с письмом пришедшим с якобы нашего домена, но не имеющее подписи.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_adsp    TXT    &amp;quot;dkim=all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DMARC =====&lt;br /&gt;
Если кратко, то это следующий уровень политик по обработке почты, который базируется на SPF и DKIM. В зависимости от результатов проверки письмо можно пропустить, отправить в карантин или отклонить.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее можно прочитать на оф. сайте https://dmarc.org/overview/ или в [https://support.google.com/a/answer/2466563?hl=ru Google].&amp;lt;br&amp;gt;&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о правилах DMARC:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_dmarc    TXT    &amp;quot;v=DMARC1; p=reject; pct=100; sp=reject; aspf=s; adkim=s; fo=1; ri=86400&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Использование SSL/TLS шифрования при подключении ====&lt;br /&gt;
===== Получение сертификатов =====&lt;br /&gt;
&lt;br /&gt;
===== Настройка сертификатов =====&lt;br /&gt;
&lt;br /&gt;
==== Отправка тестового письма с консоли ====&lt;br /&gt;
Воспользуемся telnet c почтового сервера:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
telnet localhost 25&lt;br /&gt;
HELO localhost&lt;br /&gt;
MAIL FROM:user@gwtools.ru&lt;br /&gt;
RCPT TO:user@ya.ru&lt;br /&gt;
DATA&lt;br /&gt;
SUBJECT:test subj&lt;br /&gt;
test msg&lt;br /&gt;
.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:FreeBSD]]&lt;br /&gt;
[[Category:Exim]]&lt;br /&gt;
[[Category:Mail]]&lt;br /&gt;
[[Category:SPF]]&lt;br /&gt;
[[Category:DKIM]]&lt;br /&gt;
[[Category:DMARC]]&lt;br /&gt;
[[Category:Let&#039;s Encrypt]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=653</id>
		<title>Настройка Exim на FreeBSD</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=653"/>
		<updated>2023-07-01T17:00:24Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* Настройка DKIM */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Дано: FreeBSD 13, Exim 4.&lt;br /&gt;
Задача: развернуть почтовый relay, с поддержкой шифрования.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Исторически во FreeBSD используется sendmail. Он отлично выполняет свои функции, но в современном мире сложен в настройке, т.к. конфиг представляет собой набор сложночитаемых макросов.&lt;br /&gt;
&lt;br /&gt;
===== Установка exim =====&lt;br /&gt;
Воспользуемся пакетным менеджером:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg install exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После установки будет показана страница с дальнейшими шагами, которые надо выполнить.&amp;lt;br&amp;gt;&lt;br /&gt;
Если вы закрыли консоль, то найти эту информацию можно в файле &amp;lt;code&amp;gt;/usr/local/share/doc/exim/POST-INSTALL-NOTES&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== mailer.conf =====&lt;br /&gt;
Необходимо поменять sendmail на exim в файле &amp;lt;code&amp;gt;/etc/mail/mailer.conf&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Информацию для данного файла можно взять с оф. документации в пункте [https://www.exim.org/exim-html-current/doc/html/spec%20html/ch-building%20and%20installing%20exim.html 20. Replacing another MTA with Exim]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/exim/bin/exim&lt;br /&gt;
send-mail	/usr/exim/bin/exim&lt;br /&gt;
mailq		/usr/exim/bin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так же эту информацию можно взять из &amp;lt;code&amp;gt;man 5 mailer.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате я сделал общие изменения в таком виде:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
send-mail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение sendmail =====&lt;br /&gt;
В &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; добавляем строку &amp;lt;code&amp;gt;sendmail_enable=&amp;quot;NONE&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение заданий из расписания =====&lt;br /&gt;
Теперь нужно выключить скрипты, которые выполняются по расписанию.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; По умолчанию файла &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; в системе нет.&amp;lt;br&amp;gt;&lt;br /&gt;
Создаём его и добавляем следующее содержимое (всё из той же информации после установки):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
daily_clean_hoststat_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_status_include_submit_mailq=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_submit_queuerun=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_queuerun_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Включение exim =====&lt;br /&gt;
После добавления этой опции и редактирования конфига мы сможем запустить Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;exim_enable=&amp;quot;YES&amp;quot;&#039; &amp;gt; /etc/rc.conf.d/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Ротация логов =====&lt;br /&gt;
Создаём файл &amp;lt;code&amp;gt;/etc/newsyslog.conf.d/exim.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog   mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
/var/log/exim/rejectlog mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Настройка конфига =====&lt;br /&gt;
Открываем &amp;lt;code&amp;gt;/usr/local/etc/exim/configure&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для старта необходимо не так уж и много правок.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# primary_hostname = &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
По умолчанию опция закомментирована и информация берется из системы, но я бы рекомендовал активировать эту опцию.&amp;lt;br&amp;gt;&lt;br /&gt;
Указываем адрес сервера полностью, с доменом. Если сервер работает и на локальную сеть, и на интернет, то указываем существующий и доступный домен для интернета:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
primary_hostname = mx1.gwtools.ru&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = @&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Указываем адреса и домены с доступом к почте. Разделителем является двоеточие.&amp;lt;br&amp;gt;&lt;br /&gt;
Список local_domains содержит список обслуживаемых этим сервером доменов.&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_to_domains содержит список доменов, на которые можно отправлять почту без авторизации (проверка по получателю, например, на др. почтовый сервер).&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_from_hosts содержит список адресов или доменов, с которых разрешён доступ на отправку почты через данный сервер (например, локальная сеть).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = gwtools.ru&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost : 192.168.0.0/24&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Запуск exim =====&lt;br /&gt;
Данных изменений достаточно, чтобы запустить Exim и он мог получать и отправлять почту:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service exim start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DKIM ====&lt;br /&gt;
Переходим в папке с конфигами Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd /usr/local/etc/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Приватный ключ =====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl genrsa -out gwtools.ru.private 1024&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Публичный ключ =====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl rsa -in gwtools.ru.private -pubout &amp;gt; gwtools.ru.public&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DNS ====&lt;br /&gt;
===== MX =====&lt;br /&gt;
Для того, чтобы сервера знали, куда отправлять почту для домена, нужно добавить MX-запись.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; Я рекомендую добавлять запись на почтовый сервер именно как A-запись, а не CNAME (так же, как и для NS-записей):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    MX    10 mx1.gwtools.ru&lt;br /&gt;
mx1           A     1.2.3.4&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== SPF =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о том, с какого сервера будет отправляться почта и что делать с почтой с других адресов.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее о синтаксисе SPF можно прочитать на [https://yandex.ru/support/business/domains/dns/spf.html Яндексе] или [https://support.google.com/a/answer/10683907?hl=ru Google]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    TXT    &amp;quot;v=spf1 ip4:1.2.3.4 ~all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DKIM =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о публичном ключе:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mx1    TXT    &amp;quot;v=DKIM1; k=rsa; t=s; p=ABC...XYZ&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== ADSP =====&lt;br /&gt;
ADSP (Author Domain Signing Practices, http://tools.ietf.org/search/rfc5617 ) — практики использования DKIM в домене.&amp;lt;br&amp;gt;&lt;br /&gt;
ADSP это опциональное расширение DKIM, которое позволяет сообщать почтовым серверам-получателям, что делать с письмом пришедшим с якобы нашего домена, но не имеющее подписи.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_adsp    TXT    &amp;quot;dkim=all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DMARC =====&lt;br /&gt;
Если кратко, то это следующий уровень политик по обработке почты, который базируется на SPF и DKIM. В зависимости от результатов проверки письмо можно пропустить, отправить в карантин или отклонить.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее можно прочитать на оф. сайте https://dmarc.org/overview/ или в [https://support.google.com/a/answer/2466563?hl=ru Google].&amp;lt;br&amp;gt;&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о правилах DMARC:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_dmarc    TXT    &amp;quot;v=DMARC1; p=reject; pct=100; sp=reject; aspf=s; adkim=s; fo=1; ri=86400&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Использование SSL/TLS шифрования при подключении ====&lt;br /&gt;
===== Получение сертификатов =====&lt;br /&gt;
&lt;br /&gt;
===== Настройка сертификатов =====&lt;br /&gt;
&lt;br /&gt;
==== Отправка тестового письма с консоли ====&lt;br /&gt;
Воспользуемся telnet c почтового сервера:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
telnet localhost 25&lt;br /&gt;
HELO localhost&lt;br /&gt;
MAIL FROM:user@gwtools.ru&lt;br /&gt;
RCPT TO:user@ya.ru&lt;br /&gt;
DATA&lt;br /&gt;
SUBJECT:test subj&lt;br /&gt;
test msg&lt;br /&gt;
.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:FreeBSD]]&lt;br /&gt;
[[Category:Exim]]&lt;br /&gt;
[[Category:Mail]]&lt;br /&gt;
[[Category:SPF]]&lt;br /&gt;
[[Category:DKIM]]&lt;br /&gt;
[[Category:DMARC]]&lt;br /&gt;
[[Category:Let&#039;s Encrypt]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=652</id>
		<title>Настройка Exim на FreeBSD</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=652"/>
		<updated>2023-07-01T16:59:01Z</updated>

		<summary type="html">&lt;p&gt;Bas: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Дано: FreeBSD 13, Exim 4.&lt;br /&gt;
Задача: развернуть почтовый relay, с поддержкой шифрования.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Исторически во FreeBSD используется sendmail. Он отлично выполняет свои функции, но в современном мире сложен в настройке, т.к. конфиг представляет собой набор сложночитаемых макросов.&lt;br /&gt;
&lt;br /&gt;
===== Установка exim =====&lt;br /&gt;
Воспользуемся пакетным менеджером:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg install exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После установки будет показана страница с дальнейшими шагами, которые надо выполнить.&amp;lt;br&amp;gt;&lt;br /&gt;
Если вы закрыли консоль, то найти эту информацию можно в файле &amp;lt;code&amp;gt;/usr/local/share/doc/exim/POST-INSTALL-NOTES&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== mailer.conf =====&lt;br /&gt;
Необходимо поменять sendmail на exim в файле &amp;lt;code&amp;gt;/etc/mail/mailer.conf&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Информацию для данного файла можно взять с оф. документации в пункте [https://www.exim.org/exim-html-current/doc/html/spec%20html/ch-building%20and%20installing%20exim.html 20. Replacing another MTA with Exim]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/exim/bin/exim&lt;br /&gt;
send-mail	/usr/exim/bin/exim&lt;br /&gt;
mailq		/usr/exim/bin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так же эту информацию можно взять из &amp;lt;code&amp;gt;man 5 mailer.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате я сделал общие изменения в таком виде:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
send-mail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение sendmail =====&lt;br /&gt;
В &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; добавляем строку &amp;lt;code&amp;gt;sendmail_enable=&amp;quot;NONE&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение заданий из расписания =====&lt;br /&gt;
Теперь нужно выключить скрипты, которые выполняются по расписанию.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; По умолчанию файла &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; в системе нет.&amp;lt;br&amp;gt;&lt;br /&gt;
Создаём его и добавляем следующее содержимое (всё из той же информации после установки):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
daily_clean_hoststat_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_status_include_submit_mailq=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_submit_queuerun=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_queuerun_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Включение exim =====&lt;br /&gt;
После добавления этой опции и редактирования конфига мы сможем запустить Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;exim_enable=&amp;quot;YES&amp;quot;&#039; &amp;gt; /etc/rc.conf.d/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Ротация логов =====&lt;br /&gt;
Создаём файл &amp;lt;code&amp;gt;/etc/newsyslog.conf.d/exim.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog   mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
/var/log/exim/rejectlog mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Настройка конфига =====&lt;br /&gt;
Открываем &amp;lt;code&amp;gt;/usr/local/etc/exim/configure&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для старта необходимо не так уж и много правок.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# primary_hostname = &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
По умолчанию опция закомментирована и информация берется из системы, но я бы рекомендовал активировать эту опцию.&amp;lt;br&amp;gt;&lt;br /&gt;
Указываем адрес сервера полностью, с доменом. Если сервер работает и на локальную сеть, и на интернет, то указываем существующий и доступный домен для интернета:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
primary_hostname = mx1.gwtools.ru&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = @&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Указываем адреса и домены с доступом к почте. Разделителем является двоеточие.&amp;lt;br&amp;gt;&lt;br /&gt;
Список local_domains содержит список обслуживаемых этим сервером доменов.&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_to_domains содержит список доменов, на которые можно отправлять почту без авторизации (проверка по получателю, например, на др. почтовый сервер).&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_from_hosts содержит список адресов или доменов, с которых разрешён доступ на отправку почты через данный сервер (например, локальная сеть).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = gwtools.ru&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost : 192.168.0.0/24&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Запуск exim =====&lt;br /&gt;
Данных изменений достаточно, чтобы запустить Exim и он мог получать и отправлять почту:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service exim start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DKIM ====&lt;br /&gt;
Переходим в папке с конфигами Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd /usr/local/etc/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем приватный ключ:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl genrsa -out gwtools.ru.private 1024&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем публичный ключ на основе приватного:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl rsa -in gwtools.ru.private -pubout &amp;gt; gwtools.ru.public&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DNS ====&lt;br /&gt;
===== MX =====&lt;br /&gt;
Для того, чтобы сервера знали, куда отправлять почту для домена, нужно добавить MX-запись.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; Я рекомендую добавлять запись на почтовый сервер именно как A-запись, а не CNAME (так же, как и для NS-записей):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    MX    10 mx1.gwtools.ru&lt;br /&gt;
mx1           A     1.2.3.4&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== SPF =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о том, с какого сервера будет отправляться почта и что делать с почтой с других адресов.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее о синтаксисе SPF можно прочитать на [https://yandex.ru/support/business/domains/dns/spf.html Яндексе] или [https://support.google.com/a/answer/10683907?hl=ru Google]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    TXT    &amp;quot;v=spf1 ip4:1.2.3.4 ~all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DKIM =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о публичном ключе:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mx1    TXT    &amp;quot;v=DKIM1; k=rsa; t=s; p=ABC...XYZ&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== ADSP =====&lt;br /&gt;
ADSP (Author Domain Signing Practices, http://tools.ietf.org/search/rfc5617 ) — практики использования DKIM в домене.&amp;lt;br&amp;gt;&lt;br /&gt;
ADSP это опциональное расширение DKIM, которое позволяет сообщать почтовым серверам-получателям, что делать с письмом пришедшим с якобы нашего домена, но не имеющее подписи.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_adsp    TXT    &amp;quot;dkim=all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DMARC =====&lt;br /&gt;
Если кратко, то это следующий уровень политик по обработке почты, который базируется на SPF и DKIM. В зависимости от результатов проверки письмо можно пропустить, отправить в карантин или отклонить.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее можно прочитать на оф. сайте https://dmarc.org/overview/ или в [https://support.google.com/a/answer/2466563?hl=ru Google].&amp;lt;br&amp;gt;&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о правилах DMARC:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_dmarc    TXT    &amp;quot;v=DMARC1; p=reject; pct=100; sp=reject; aspf=s; adkim=s; fo=1; ri=86400&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Использование SSL/TLS шифрования при подключении ====&lt;br /&gt;
===== Получение сертификатов =====&lt;br /&gt;
&lt;br /&gt;
===== Настройка сертификатов =====&lt;br /&gt;
&lt;br /&gt;
==== Отправка тестового письма с консоли ====&lt;br /&gt;
Воспользуемся telnet c почтового сервера:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
telnet localhost 25&lt;br /&gt;
HELO localhost&lt;br /&gt;
MAIL FROM:user@gwtools.ru&lt;br /&gt;
RCPT TO:user@ya.ru&lt;br /&gt;
DATA&lt;br /&gt;
SUBJECT:test subj&lt;br /&gt;
test msg&lt;br /&gt;
.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:FreeBSD]]&lt;br /&gt;
[[Category:Exim]]&lt;br /&gt;
[[Category:Mail]]&lt;br /&gt;
[[Category:SPF]]&lt;br /&gt;
[[Category:DKIM]]&lt;br /&gt;
[[Category:DMARC]]&lt;br /&gt;
[[Category:Let&#039;s Encrypt]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=651</id>
		<title>Настройка Exim на FreeBSD</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=651"/>
		<updated>2023-07-01T16:58:16Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* DMARC */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Дано: FreeBSD 13, Exim 4.&lt;br /&gt;
Задача: развернуть почтовый relay, с поддержкой шифрования.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Исторически во FreeBSD используется sendmail. Он отлично выполняет свои функции, но в современном мире сложен в настройке, т.к. конфиг представляет собой набор сложночитаемых макросов.&lt;br /&gt;
&lt;br /&gt;
===== Установка exim =====&lt;br /&gt;
Воспользуемся пакетным менеджером:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg install exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После установки будет показана страница с дальнейшими шагами, которые надо выполнить.&amp;lt;br&amp;gt;&lt;br /&gt;
Если вы закрыли консоль, то найти эту информацию можно в файле &amp;lt;code&amp;gt;/usr/local/share/doc/exim/POST-INSTALL-NOTES&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== mailer.conf =====&lt;br /&gt;
Необходимо поменять sendmail на exim в файле &amp;lt;code&amp;gt;/etc/mail/mailer.conf&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Информацию для данного файла можно взять с оф. документации в пункте [https://www.exim.org/exim-html-current/doc/html/spec%20html/ch-building%20and%20installing%20exim.html 20. Replacing another MTA with Exim]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/exim/bin/exim&lt;br /&gt;
send-mail	/usr/exim/bin/exim&lt;br /&gt;
mailq		/usr/exim/bin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так же эту информацию можно взять из &amp;lt;code&amp;gt;man 5 mailer.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате я сделал общие изменения в таком виде:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
send-mail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение sendmail =====&lt;br /&gt;
В &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; добавляем строку &amp;lt;code&amp;gt;sendmail_enable=&amp;quot;NONE&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение заданий из расписания =====&lt;br /&gt;
Теперь нужно выключить скрипты, которые выполняются по расписанию.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; По умолчанию файла &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; в системе нет.&amp;lt;br&amp;gt;&lt;br /&gt;
Создаём его и добавляем следующее содержимое (всё из той же информации после установки):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
daily_clean_hoststat_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_status_include_submit_mailq=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_submit_queuerun=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_queuerun_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Включение exim =====&lt;br /&gt;
После добавления этой опции и редактирования конфига мы сможем запустить Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;exim_enable=&amp;quot;YES&amp;quot;&#039; &amp;gt; /etc/rc.conf.d/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Ротация логов =====&lt;br /&gt;
Создаём файл &amp;lt;code&amp;gt;/etc/newsyslog.conf.d/exim.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog   mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
/var/log/exim/rejectlog mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Настройка конфига =====&lt;br /&gt;
Открываем &amp;lt;code&amp;gt;/usr/local/etc/exim/configure&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для старта необходимо не так уж и много правок.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# primary_hostname = &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
По умолчанию опция закомментирована и информация берется из системы, но я бы рекомендовал активировать эту опцию.&amp;lt;br&amp;gt;&lt;br /&gt;
Указываем адрес сервера полностью, с доменом. Если сервер работает и на локальную сеть, и на интернет, то указываем существующий и доступный домен для интернета:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
primary_hostname = mx1.gwtools.ru&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = @&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Указываем адреса и домены с доступом к почте. Разделителем является двоеточие.&amp;lt;br&amp;gt;&lt;br /&gt;
Список local_domains содержит список обслуживаемых этим сервером доменов.&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_to_domains содержит список доменов, на которые можно отправлять почту без авторизации (проверка по получателю, например, на др. почтовый сервер).&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_from_hosts содержит список адресов или доменов, с которых разрешён доступ на отправку почты через данный сервер (например, локальная сеть).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = gwtools.ru&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost : 192.168.0.0/24&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Запуск exim =====&lt;br /&gt;
Данных изменений достаточно, чтобы запустить Exim и он мог получать и отправлять почту:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service exim start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DKIM ====&lt;br /&gt;
Переходим в папке с конфигами Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd /usr/local/etc/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем приватный ключ:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl genrsa -out gwtools.ru.private 1024&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем публичный ключ на основе приватного:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl rsa -in gwtools.ru.private -pubout &amp;gt; gwtools.ru.public&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DNS ====&lt;br /&gt;
===== MX =====&lt;br /&gt;
Для того, чтобы сервера знали, куда отправлять почту для домена, нужно добавить MX-запись.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; Я рекомендую добавлять запись на почтовый сервер именно как A-запись, а не CNAME (так же, как и для NS-записей):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    MX    10 mx1.gwtools.ru&lt;br /&gt;
mx1           A     1.2.3.4&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== SPF =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о том, с какого сервера будет отправляться почта и что делать с почтой с других адресов.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее о синтаксисе SPF можно прочитать на [https://yandex.ru/support/business/domains/dns/spf.html Яндексе] или [https://support.google.com/a/answer/10683907?hl=ru Google]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    TXT    &amp;quot;v=spf1 ip4:1.2.3.4 ~all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DKIM =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о публичном ключе:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mx1    TXT    &amp;quot;v=DKIM1; k=rsa; t=s; p=ABC...XYZ&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== ADSP =====&lt;br /&gt;
ADSP (Author Domain Signing Practices, http://tools.ietf.org/search/rfc5617 ) — практики использования DKIM в домене.&amp;lt;br&amp;gt;&lt;br /&gt;
ADSP это опциональное расширение DKIM, которое позволяет сообщать почтовым серверам-получателям, что делать с письмом пришедшим с якобы нашего домена, но не имеющее подписи.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_adsp    TXT    &amp;quot;dkim=all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DMARC =====&lt;br /&gt;
Если кратко, то это следующий уровень политик по обработке почты, который базируется на SPF и DKIM. В зависимости от результатов проверки письмо можно пропустить, отправить в карантин или отклонить.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее можно прочитать на оф. сайте https://dmarc.org/overview/ или в [https://support.google.com/a/answer/2466563?hl=ru Google].&amp;lt;br&amp;gt;&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о правилах DMARC:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_dmarc    TXT    &amp;quot;v=DMARC1; p=reject; pct=100; sp=reject; aspf=s; adkim=s; fo=1; ri=86400&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Использование SSL/TLS шифрования при подключении ====&lt;br /&gt;
===== Получение сертификатов =====&lt;br /&gt;
&lt;br /&gt;
===== Настройка сертификатов =====&lt;br /&gt;
&lt;br /&gt;
==== Отправка тестового письма с консоли ====&lt;br /&gt;
Воспользуемся telnet c почтового сервера:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
telnet localhost 25&lt;br /&gt;
HELO localhost&lt;br /&gt;
MAIL FROM:user@gwtools.ru&lt;br /&gt;
RCPT TO:user@ya.ru&lt;br /&gt;
DATA&lt;br /&gt;
SUBJECT:test subj&lt;br /&gt;
test msg&lt;br /&gt;
.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:FreeBSD]]&lt;br /&gt;
[[Category:Exim]]&lt;br /&gt;
[[Category:Mail]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=650</id>
		<title>Настройка Exim на FreeBSD</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=650"/>
		<updated>2023-07-01T16:56:24Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* DMARC */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Дано: FreeBSD 13, Exim 4.&lt;br /&gt;
Задача: развернуть почтовый relay, с поддержкой шифрования.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Исторически во FreeBSD используется sendmail. Он отлично выполняет свои функции, но в современном мире сложен в настройке, т.к. конфиг представляет собой набор сложночитаемых макросов.&lt;br /&gt;
&lt;br /&gt;
===== Установка exim =====&lt;br /&gt;
Воспользуемся пакетным менеджером:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg install exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После установки будет показана страница с дальнейшими шагами, которые надо выполнить.&amp;lt;br&amp;gt;&lt;br /&gt;
Если вы закрыли консоль, то найти эту информацию можно в файле &amp;lt;code&amp;gt;/usr/local/share/doc/exim/POST-INSTALL-NOTES&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== mailer.conf =====&lt;br /&gt;
Необходимо поменять sendmail на exim в файле &amp;lt;code&amp;gt;/etc/mail/mailer.conf&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Информацию для данного файла можно взять с оф. документации в пункте [https://www.exim.org/exim-html-current/doc/html/spec%20html/ch-building%20and%20installing%20exim.html 20. Replacing another MTA with Exim]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/exim/bin/exim&lt;br /&gt;
send-mail	/usr/exim/bin/exim&lt;br /&gt;
mailq		/usr/exim/bin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так же эту информацию можно взять из &amp;lt;code&amp;gt;man 5 mailer.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате я сделал общие изменения в таком виде:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
send-mail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение sendmail =====&lt;br /&gt;
В &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; добавляем строку &amp;lt;code&amp;gt;sendmail_enable=&amp;quot;NONE&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение заданий из расписания =====&lt;br /&gt;
Теперь нужно выключить скрипты, которые выполняются по расписанию.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; По умолчанию файла &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; в системе нет.&amp;lt;br&amp;gt;&lt;br /&gt;
Создаём его и добавляем следующее содержимое (всё из той же информации после установки):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
daily_clean_hoststat_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_status_include_submit_mailq=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_submit_queuerun=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_queuerun_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Включение exim =====&lt;br /&gt;
После добавления этой опции и редактирования конфига мы сможем запустить Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;exim_enable=&amp;quot;YES&amp;quot;&#039; &amp;gt; /etc/rc.conf.d/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Ротация логов =====&lt;br /&gt;
Создаём файл &amp;lt;code&amp;gt;/etc/newsyslog.conf.d/exim.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog   mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
/var/log/exim/rejectlog mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Настройка конфига =====&lt;br /&gt;
Открываем &amp;lt;code&amp;gt;/usr/local/etc/exim/configure&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для старта необходимо не так уж и много правок.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# primary_hostname = &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
По умолчанию опция закомментирована и информация берется из системы, но я бы рекомендовал активировать эту опцию.&amp;lt;br&amp;gt;&lt;br /&gt;
Указываем адрес сервера полностью, с доменом. Если сервер работает и на локальную сеть, и на интернет, то указываем существующий и доступный домен для интернета:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
primary_hostname = mx1.gwtools.ru&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = @&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Указываем адреса и домены с доступом к почте. Разделителем является двоеточие.&amp;lt;br&amp;gt;&lt;br /&gt;
Список local_domains содержит список обслуживаемых этим сервером доменов.&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_to_domains содержит список доменов, на которые можно отправлять почту без авторизации (проверка по получателю, например, на др. почтовый сервер).&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_from_hosts содержит список адресов или доменов, с которых разрешён доступ на отправку почты через данный сервер (например, локальная сеть).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = gwtools.ru&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost : 192.168.0.0/24&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Запуск exim =====&lt;br /&gt;
Данных изменений достаточно, чтобы запустить Exim и он мог получать и отправлять почту:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service exim start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DKIM ====&lt;br /&gt;
Переходим в папке с конфигами Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd /usr/local/etc/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем приватный ключ:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl genrsa -out gwtools.ru.private 1024&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем публичный ключ на основе приватного:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl rsa -in gwtools.ru.private -pubout &amp;gt; gwtools.ru.public&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DNS ====&lt;br /&gt;
===== MX =====&lt;br /&gt;
Для того, чтобы сервера знали, куда отправлять почту для домена, нужно добавить MX-запись.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; Я рекомендую добавлять запись на почтовый сервер именно как A-запись, а не CNAME (так же, как и для NS-записей):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    MX    10 mx1.gwtools.ru&lt;br /&gt;
mx1           A     1.2.3.4&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== SPF =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о том, с какого сервера будет отправляться почта и что делать с почтой с других адресов.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее о синтаксисе SPF можно прочитать на [https://yandex.ru/support/business/domains/dns/spf.html Яндексе] или [https://support.google.com/a/answer/10683907?hl=ru Google]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    TXT    &amp;quot;v=spf1 ip4:1.2.3.4 ~all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DKIM =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о публичном ключе:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mx1    TXT    &amp;quot;v=DKIM1; k=rsa; t=s; p=ABC...XYZ&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== ADSP =====&lt;br /&gt;
ADSP (Author Domain Signing Practices, http://tools.ietf.org/search/rfc5617 ) — практики использования DKIM в домене.&amp;lt;br&amp;gt;&lt;br /&gt;
ADSP это опциональное расширение DKIM, которое позволяет сообщать почтовым серверам-получателям, что делать с письмом пришедшим с якобы нашего домена, но не имеющее подписи.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_adsp    TXT    &amp;quot;dkim=all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DMARC =====&lt;br /&gt;
Подробнее можно прочитать на оф. сайте https://dmarc.org/overview/ или в [https://support.google.com/a/answer/2466563?hl=ru Google].&amp;lt;br&amp;gt;&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о правилах DMARC:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_dmarc    TXT    &amp;quot;v=DMARC1; p=reject; pct=100; sp=reject; aspf=s; adkim=s; fo=1; ri=86400&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Использование SSL/TLS шифрования при подключении ====&lt;br /&gt;
===== Получение сертификатов =====&lt;br /&gt;
&lt;br /&gt;
===== Настройка сертификатов =====&lt;br /&gt;
&lt;br /&gt;
==== Отправка тестового письма с консоли ====&lt;br /&gt;
Воспользуемся telnet c почтового сервера:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
telnet localhost 25&lt;br /&gt;
HELO localhost&lt;br /&gt;
MAIL FROM:user@gwtools.ru&lt;br /&gt;
RCPT TO:user@ya.ru&lt;br /&gt;
DATA&lt;br /&gt;
SUBJECT:test subj&lt;br /&gt;
test msg&lt;br /&gt;
.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:FreeBSD]]&lt;br /&gt;
[[Category:Exim]]&lt;br /&gt;
[[Category:Mail]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=649</id>
		<title>Настройка Exim на FreeBSD</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=649"/>
		<updated>2023-07-01T16:53:59Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* Настройка сертификатов */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Дано: FreeBSD 13, Exim 4.&lt;br /&gt;
Задача: развернуть почтовый relay, с поддержкой шифрования.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Исторически во FreeBSD используется sendmail. Он отлично выполняет свои функции, но в современном мире сложен в настройке, т.к. конфиг представляет собой набор сложночитаемых макросов.&lt;br /&gt;
&lt;br /&gt;
===== Установка exim =====&lt;br /&gt;
Воспользуемся пакетным менеджером:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg install exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После установки будет показана страница с дальнейшими шагами, которые надо выполнить.&amp;lt;br&amp;gt;&lt;br /&gt;
Если вы закрыли консоль, то найти эту информацию можно в файле &amp;lt;code&amp;gt;/usr/local/share/doc/exim/POST-INSTALL-NOTES&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== mailer.conf =====&lt;br /&gt;
Необходимо поменять sendmail на exim в файле &amp;lt;code&amp;gt;/etc/mail/mailer.conf&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Информацию для данного файла можно взять с оф. документации в пункте [https://www.exim.org/exim-html-current/doc/html/spec%20html/ch-building%20and%20installing%20exim.html 20. Replacing another MTA with Exim]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/exim/bin/exim&lt;br /&gt;
send-mail	/usr/exim/bin/exim&lt;br /&gt;
mailq		/usr/exim/bin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так же эту информацию можно взять из &amp;lt;code&amp;gt;man 5 mailer.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате я сделал общие изменения в таком виде:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
send-mail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение sendmail =====&lt;br /&gt;
В &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; добавляем строку &amp;lt;code&amp;gt;sendmail_enable=&amp;quot;NONE&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение заданий из расписания =====&lt;br /&gt;
Теперь нужно выключить скрипты, которые выполняются по расписанию.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; По умолчанию файла &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; в системе нет.&amp;lt;br&amp;gt;&lt;br /&gt;
Создаём его и добавляем следующее содержимое (всё из той же информации после установки):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
daily_clean_hoststat_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_status_include_submit_mailq=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_submit_queuerun=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_queuerun_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Включение exim =====&lt;br /&gt;
После добавления этой опции и редактирования конфига мы сможем запустить Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;exim_enable=&amp;quot;YES&amp;quot;&#039; &amp;gt; /etc/rc.conf.d/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Ротация логов =====&lt;br /&gt;
Создаём файл &amp;lt;code&amp;gt;/etc/newsyslog.conf.d/exim.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog   mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
/var/log/exim/rejectlog mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Настройка конфига =====&lt;br /&gt;
Открываем &amp;lt;code&amp;gt;/usr/local/etc/exim/configure&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для старта необходимо не так уж и много правок.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# primary_hostname = &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
По умолчанию опция закомментирована и информация берется из системы, но я бы рекомендовал активировать эту опцию.&amp;lt;br&amp;gt;&lt;br /&gt;
Указываем адрес сервера полностью, с доменом. Если сервер работает и на локальную сеть, и на интернет, то указываем существующий и доступный домен для интернета:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
primary_hostname = mx1.gwtools.ru&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = @&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Указываем адреса и домены с доступом к почте. Разделителем является двоеточие.&amp;lt;br&amp;gt;&lt;br /&gt;
Список local_domains содержит список обслуживаемых этим сервером доменов.&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_to_domains содержит список доменов, на которые можно отправлять почту без авторизации (проверка по получателю, например, на др. почтовый сервер).&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_from_hosts содержит список адресов или доменов, с которых разрешён доступ на отправку почты через данный сервер (например, локальная сеть).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = gwtools.ru&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost : 192.168.0.0/24&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Запуск exim =====&lt;br /&gt;
Данных изменений достаточно, чтобы запустить Exim и он мог получать и отправлять почту:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service exim start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DKIM ====&lt;br /&gt;
Переходим в папке с конфигами Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd /usr/local/etc/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем приватный ключ:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl genrsa -out gwtools.ru.private 1024&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем публичный ключ на основе приватного:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl rsa -in gwtools.ru.private -pubout &amp;gt; gwtools.ru.public&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DNS ====&lt;br /&gt;
===== MX =====&lt;br /&gt;
Для того, чтобы сервера знали, куда отправлять почту для домена, нужно добавить MX-запись.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; Я рекомендую добавлять запись на почтовый сервер именно как A-запись, а не CNAME (так же, как и для NS-записей):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    MX    10 mx1.gwtools.ru&lt;br /&gt;
mx1           A     1.2.3.4&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== SPF =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о том, с какого сервера будет отправляться почта и что делать с почтой с других адресов.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее о синтаксисе SPF можно прочитать на [https://yandex.ru/support/business/domains/dns/spf.html Яндексе] или [https://support.google.com/a/answer/10683907?hl=ru Google]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    TXT    &amp;quot;v=spf1 ip4:1.2.3.4 ~all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DKIM =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о публичном ключе:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mx1    TXT    &amp;quot;v=DKIM1; k=rsa; t=s; p=ABC...XYZ&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== ADSP =====&lt;br /&gt;
ADSP (Author Domain Signing Practices, http://tools.ietf.org/search/rfc5617 ) — практики использования DKIM в домене.&amp;lt;br&amp;gt;&lt;br /&gt;
ADSP это опциональное расширение DKIM, которое позволяет сообщать почтовым серверам-получателям, что делать с письмом пришедшим с якобы нашего домена, но не имеющее подписи.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_adsp    TXT    &amp;quot;dkim=all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DMARC =====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_dmarc    TXT    &amp;quot;v=DMARC1; p=reject; pct=100; sp=reject; aspf=s; adkim=s; fo=1; ri=86400&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Использование SSL/TLS шифрования при подключении ====&lt;br /&gt;
===== Получение сертификатов =====&lt;br /&gt;
&lt;br /&gt;
===== Настройка сертификатов =====&lt;br /&gt;
&lt;br /&gt;
==== Отправка тестового письма с консоли ====&lt;br /&gt;
Воспользуемся telnet c почтового сервера:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
telnet localhost 25&lt;br /&gt;
HELO localhost&lt;br /&gt;
MAIL FROM:user@gwtools.ru&lt;br /&gt;
RCPT TO:user@ya.ru&lt;br /&gt;
DATA&lt;br /&gt;
SUBJECT:test subj&lt;br /&gt;
test msg&lt;br /&gt;
.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:FreeBSD]]&lt;br /&gt;
[[Category:Exim]]&lt;br /&gt;
[[Category:Mail]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=648</id>
		<title>Настройка Exim на FreeBSD</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=648"/>
		<updated>2023-07-01T16:53:52Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* Получение сертификатов */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Дано: FreeBSD 13, Exim 4.&lt;br /&gt;
Задача: развернуть почтовый relay, с поддержкой шифрования.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Исторически во FreeBSD используется sendmail. Он отлично выполняет свои функции, но в современном мире сложен в настройке, т.к. конфиг представляет собой набор сложночитаемых макросов.&lt;br /&gt;
&lt;br /&gt;
===== Установка exim =====&lt;br /&gt;
Воспользуемся пакетным менеджером:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg install exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После установки будет показана страница с дальнейшими шагами, которые надо выполнить.&amp;lt;br&amp;gt;&lt;br /&gt;
Если вы закрыли консоль, то найти эту информацию можно в файле &amp;lt;code&amp;gt;/usr/local/share/doc/exim/POST-INSTALL-NOTES&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== mailer.conf =====&lt;br /&gt;
Необходимо поменять sendmail на exim в файле &amp;lt;code&amp;gt;/etc/mail/mailer.conf&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Информацию для данного файла можно взять с оф. документации в пункте [https://www.exim.org/exim-html-current/doc/html/spec%20html/ch-building%20and%20installing%20exim.html 20. Replacing another MTA with Exim]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/exim/bin/exim&lt;br /&gt;
send-mail	/usr/exim/bin/exim&lt;br /&gt;
mailq		/usr/exim/bin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так же эту информацию можно взять из &amp;lt;code&amp;gt;man 5 mailer.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате я сделал общие изменения в таком виде:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
send-mail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение sendmail =====&lt;br /&gt;
В &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; добавляем строку &amp;lt;code&amp;gt;sendmail_enable=&amp;quot;NONE&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение заданий из расписания =====&lt;br /&gt;
Теперь нужно выключить скрипты, которые выполняются по расписанию.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; По умолчанию файла &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; в системе нет.&amp;lt;br&amp;gt;&lt;br /&gt;
Создаём его и добавляем следующее содержимое (всё из той же информации после установки):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
daily_clean_hoststat_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_status_include_submit_mailq=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_submit_queuerun=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_queuerun_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Включение exim =====&lt;br /&gt;
После добавления этой опции и редактирования конфига мы сможем запустить Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;exim_enable=&amp;quot;YES&amp;quot;&#039; &amp;gt; /etc/rc.conf.d/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Ротация логов =====&lt;br /&gt;
Создаём файл &amp;lt;code&amp;gt;/etc/newsyslog.conf.d/exim.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog   mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
/var/log/exim/rejectlog mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Настройка конфига =====&lt;br /&gt;
Открываем &amp;lt;code&amp;gt;/usr/local/etc/exim/configure&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для старта необходимо не так уж и много правок.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# primary_hostname = &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
По умолчанию опция закомментирована и информация берется из системы, но я бы рекомендовал активировать эту опцию.&amp;lt;br&amp;gt;&lt;br /&gt;
Указываем адрес сервера полностью, с доменом. Если сервер работает и на локальную сеть, и на интернет, то указываем существующий и доступный домен для интернета:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
primary_hostname = mx1.gwtools.ru&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = @&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Указываем адреса и домены с доступом к почте. Разделителем является двоеточие.&amp;lt;br&amp;gt;&lt;br /&gt;
Список local_domains содержит список обслуживаемых этим сервером доменов.&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_to_domains содержит список доменов, на которые можно отправлять почту без авторизации (проверка по получателю, например, на др. почтовый сервер).&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_from_hosts содержит список адресов или доменов, с которых разрешён доступ на отправку почты через данный сервер (например, локальная сеть).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = gwtools.ru&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost : 192.168.0.0/24&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Запуск exim =====&lt;br /&gt;
Данных изменений достаточно, чтобы запустить Exim и он мог получать и отправлять почту:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service exim start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DKIM ====&lt;br /&gt;
Переходим в папке с конфигами Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd /usr/local/etc/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем приватный ключ:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl genrsa -out gwtools.ru.private 1024&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем публичный ключ на основе приватного:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl rsa -in gwtools.ru.private -pubout &amp;gt; gwtools.ru.public&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DNS ====&lt;br /&gt;
===== MX =====&lt;br /&gt;
Для того, чтобы сервера знали, куда отправлять почту для домена, нужно добавить MX-запись.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; Я рекомендую добавлять запись на почтовый сервер именно как A-запись, а не CNAME (так же, как и для NS-записей):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    MX    10 mx1.gwtools.ru&lt;br /&gt;
mx1           A     1.2.3.4&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== SPF =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о том, с какого сервера будет отправляться почта и что делать с почтой с других адресов.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее о синтаксисе SPF можно прочитать на [https://yandex.ru/support/business/domains/dns/spf.html Яндексе] или [https://support.google.com/a/answer/10683907?hl=ru Google]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    TXT    &amp;quot;v=spf1 ip4:1.2.3.4 ~all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DKIM =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о публичном ключе:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mx1    TXT    &amp;quot;v=DKIM1; k=rsa; t=s; p=ABC...XYZ&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== ADSP =====&lt;br /&gt;
ADSP (Author Domain Signing Practices, http://tools.ietf.org/search/rfc5617 ) — практики использования DKIM в домене.&amp;lt;br&amp;gt;&lt;br /&gt;
ADSP это опциональное расширение DKIM, которое позволяет сообщать почтовым серверам-получателям, что делать с письмом пришедшим с якобы нашего домена, но не имеющее подписи.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_adsp    TXT    &amp;quot;dkim=all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DMARC =====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_dmarc    TXT    &amp;quot;v=DMARC1; p=reject; pct=100; sp=reject; aspf=s; adkim=s; fo=1; ri=86400&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Использование SSL/TLS шифрования при подключении ====&lt;br /&gt;
===== Получение сертификатов =====&lt;br /&gt;
&lt;br /&gt;
==== Настройка сертификатов ====&lt;br /&gt;
&lt;br /&gt;
==== Отправка тестового письма с консоли ====&lt;br /&gt;
Воспользуемся telnet c почтового сервера:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
telnet localhost 25&lt;br /&gt;
HELO localhost&lt;br /&gt;
MAIL FROM:user@gwtools.ru&lt;br /&gt;
RCPT TO:user@ya.ru&lt;br /&gt;
DATA&lt;br /&gt;
SUBJECT:test subj&lt;br /&gt;
test msg&lt;br /&gt;
.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:FreeBSD]]&lt;br /&gt;
[[Category:Exim]]&lt;br /&gt;
[[Category:Mail]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=647</id>
		<title>Настройка Exim на FreeBSD</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=647"/>
		<updated>2023-07-01T16:53:06Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* Настройка DMARC */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Дано: FreeBSD 13, Exim 4.&lt;br /&gt;
Задача: развернуть почтовый relay, с поддержкой шифрования.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Исторически во FreeBSD используется sendmail. Он отлично выполняет свои функции, но в современном мире сложен в настройке, т.к. конфиг представляет собой набор сложночитаемых макросов.&lt;br /&gt;
&lt;br /&gt;
===== Установка exim =====&lt;br /&gt;
Воспользуемся пакетным менеджером:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg install exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После установки будет показана страница с дальнейшими шагами, которые надо выполнить.&amp;lt;br&amp;gt;&lt;br /&gt;
Если вы закрыли консоль, то найти эту информацию можно в файле &amp;lt;code&amp;gt;/usr/local/share/doc/exim/POST-INSTALL-NOTES&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== mailer.conf =====&lt;br /&gt;
Необходимо поменять sendmail на exim в файле &amp;lt;code&amp;gt;/etc/mail/mailer.conf&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Информацию для данного файла можно взять с оф. документации в пункте [https://www.exim.org/exim-html-current/doc/html/spec%20html/ch-building%20and%20installing%20exim.html 20. Replacing another MTA with Exim]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/exim/bin/exim&lt;br /&gt;
send-mail	/usr/exim/bin/exim&lt;br /&gt;
mailq		/usr/exim/bin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так же эту информацию можно взять из &amp;lt;code&amp;gt;man 5 mailer.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате я сделал общие изменения в таком виде:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
send-mail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение sendmail =====&lt;br /&gt;
В &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; добавляем строку &amp;lt;code&amp;gt;sendmail_enable=&amp;quot;NONE&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение заданий из расписания =====&lt;br /&gt;
Теперь нужно выключить скрипты, которые выполняются по расписанию.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; По умолчанию файла &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; в системе нет.&amp;lt;br&amp;gt;&lt;br /&gt;
Создаём его и добавляем следующее содержимое (всё из той же информации после установки):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
daily_clean_hoststat_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_status_include_submit_mailq=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_submit_queuerun=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_queuerun_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Включение exim =====&lt;br /&gt;
После добавления этой опции и редактирования конфига мы сможем запустить Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;exim_enable=&amp;quot;YES&amp;quot;&#039; &amp;gt; /etc/rc.conf.d/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Ротация логов =====&lt;br /&gt;
Создаём файл &amp;lt;code&amp;gt;/etc/newsyslog.conf.d/exim.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog   mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
/var/log/exim/rejectlog mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Настройка конфига =====&lt;br /&gt;
Открываем &amp;lt;code&amp;gt;/usr/local/etc/exim/configure&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для старта необходимо не так уж и много правок.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# primary_hostname = &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
По умолчанию опция закомментирована и информация берется из системы, но я бы рекомендовал активировать эту опцию.&amp;lt;br&amp;gt;&lt;br /&gt;
Указываем адрес сервера полностью, с доменом. Если сервер работает и на локальную сеть, и на интернет, то указываем существующий и доступный домен для интернета:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
primary_hostname = mx1.gwtools.ru&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = @&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Указываем адреса и домены с доступом к почте. Разделителем является двоеточие.&amp;lt;br&amp;gt;&lt;br /&gt;
Список local_domains содержит список обслуживаемых этим сервером доменов.&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_to_domains содержит список доменов, на которые можно отправлять почту без авторизации (проверка по получателю, например, на др. почтовый сервер).&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_from_hosts содержит список адресов или доменов, с которых разрешён доступ на отправку почты через данный сервер (например, локальная сеть).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = gwtools.ru&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost : 192.168.0.0/24&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Запуск exim =====&lt;br /&gt;
Данных изменений достаточно, чтобы запустить Exim и он мог получать и отправлять почту:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service exim start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DKIM ====&lt;br /&gt;
Переходим в папке с конфигами Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd /usr/local/etc/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем приватный ключ:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl genrsa -out gwtools.ru.private 1024&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем публичный ключ на основе приватного:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl rsa -in gwtools.ru.private -pubout &amp;gt; gwtools.ru.public&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DNS ====&lt;br /&gt;
===== MX =====&lt;br /&gt;
Для того, чтобы сервера знали, куда отправлять почту для домена, нужно добавить MX-запись.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; Я рекомендую добавлять запись на почтовый сервер именно как A-запись, а не CNAME (так же, как и для NS-записей):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    MX    10 mx1.gwtools.ru&lt;br /&gt;
mx1           A     1.2.3.4&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== SPF =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о том, с какого сервера будет отправляться почта и что делать с почтой с других адресов.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее о синтаксисе SPF можно прочитать на [https://yandex.ru/support/business/domains/dns/spf.html Яндексе] или [https://support.google.com/a/answer/10683907?hl=ru Google]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    TXT    &amp;quot;v=spf1 ip4:1.2.3.4 ~all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DKIM =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о публичном ключе:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mx1    TXT    &amp;quot;v=DKIM1; k=rsa; t=s; p=ABC...XYZ&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== ADSP =====&lt;br /&gt;
ADSP (Author Domain Signing Practices, http://tools.ietf.org/search/rfc5617 ) — практики использования DKIM в домене.&amp;lt;br&amp;gt;&lt;br /&gt;
ADSP это опциональное расширение DKIM, которое позволяет сообщать почтовым серверам-получателям, что делать с письмом пришедшим с якобы нашего домена, но не имеющее подписи.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_adsp    TXT    &amp;quot;dkim=all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DMARC =====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_dmarc    TXT    &amp;quot;v=DMARC1; p=reject; pct=100; sp=reject; aspf=s; adkim=s; fo=1; ri=86400&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Получение сертификатов ====&lt;br /&gt;
&lt;br /&gt;
==== Настройка сертификатов ====&lt;br /&gt;
&lt;br /&gt;
==== Отправка тестового письма с консоли ====&lt;br /&gt;
Воспользуемся telnet c почтового сервера:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
telnet localhost 25&lt;br /&gt;
HELO localhost&lt;br /&gt;
MAIL FROM:user@gwtools.ru&lt;br /&gt;
RCPT TO:user@ya.ru&lt;br /&gt;
DATA&lt;br /&gt;
SUBJECT:test subj&lt;br /&gt;
test msg&lt;br /&gt;
.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:FreeBSD]]&lt;br /&gt;
[[Category:Exim]]&lt;br /&gt;
[[Category:Mail]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=646</id>
		<title>Настройка Exim на FreeBSD</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=646"/>
		<updated>2023-07-01T16:52:32Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* ADSP */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Дано: FreeBSD 13, Exim 4.&lt;br /&gt;
Задача: развернуть почтовый relay, с поддержкой шифрования.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Исторически во FreeBSD используется sendmail. Он отлично выполняет свои функции, но в современном мире сложен в настройке, т.к. конфиг представляет собой набор сложночитаемых макросов.&lt;br /&gt;
&lt;br /&gt;
===== Установка exim =====&lt;br /&gt;
Воспользуемся пакетным менеджером:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg install exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После установки будет показана страница с дальнейшими шагами, которые надо выполнить.&amp;lt;br&amp;gt;&lt;br /&gt;
Если вы закрыли консоль, то найти эту информацию можно в файле &amp;lt;code&amp;gt;/usr/local/share/doc/exim/POST-INSTALL-NOTES&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== mailer.conf =====&lt;br /&gt;
Необходимо поменять sendmail на exim в файле &amp;lt;code&amp;gt;/etc/mail/mailer.conf&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Информацию для данного файла можно взять с оф. документации в пункте [https://www.exim.org/exim-html-current/doc/html/spec%20html/ch-building%20and%20installing%20exim.html 20. Replacing another MTA with Exim]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/exim/bin/exim&lt;br /&gt;
send-mail	/usr/exim/bin/exim&lt;br /&gt;
mailq		/usr/exim/bin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так же эту информацию можно взять из &amp;lt;code&amp;gt;man 5 mailer.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате я сделал общие изменения в таком виде:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
send-mail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение sendmail =====&lt;br /&gt;
В &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; добавляем строку &amp;lt;code&amp;gt;sendmail_enable=&amp;quot;NONE&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение заданий из расписания =====&lt;br /&gt;
Теперь нужно выключить скрипты, которые выполняются по расписанию.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; По умолчанию файла &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; в системе нет.&amp;lt;br&amp;gt;&lt;br /&gt;
Создаём его и добавляем следующее содержимое (всё из той же информации после установки):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
daily_clean_hoststat_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_status_include_submit_mailq=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_submit_queuerun=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_queuerun_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Включение exim =====&lt;br /&gt;
После добавления этой опции и редактирования конфига мы сможем запустить Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;exim_enable=&amp;quot;YES&amp;quot;&#039; &amp;gt; /etc/rc.conf.d/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Ротация логов =====&lt;br /&gt;
Создаём файл &amp;lt;code&amp;gt;/etc/newsyslog.conf.d/exim.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog   mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
/var/log/exim/rejectlog mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Настройка конфига =====&lt;br /&gt;
Открываем &amp;lt;code&amp;gt;/usr/local/etc/exim/configure&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для старта необходимо не так уж и много правок.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# primary_hostname = &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
По умолчанию опция закомментирована и информация берется из системы, но я бы рекомендовал активировать эту опцию.&amp;lt;br&amp;gt;&lt;br /&gt;
Указываем адрес сервера полностью, с доменом. Если сервер работает и на локальную сеть, и на интернет, то указываем существующий и доступный домен для интернета:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
primary_hostname = mx1.gwtools.ru&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = @&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Указываем адреса и домены с доступом к почте. Разделителем является двоеточие.&amp;lt;br&amp;gt;&lt;br /&gt;
Список local_domains содержит список обслуживаемых этим сервером доменов.&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_to_domains содержит список доменов, на которые можно отправлять почту без авторизации (проверка по получателю, например, на др. почтовый сервер).&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_from_hosts содержит список адресов или доменов, с которых разрешён доступ на отправку почты через данный сервер (например, локальная сеть).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = gwtools.ru&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost : 192.168.0.0/24&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Запуск exim =====&lt;br /&gt;
Данных изменений достаточно, чтобы запустить Exim и он мог получать и отправлять почту:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service exim start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DKIM ====&lt;br /&gt;
Переходим в папке с конфигами Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd /usr/local/etc/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем приватный ключ:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl genrsa -out gwtools.ru.private 1024&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем публичный ключ на основе приватного:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl rsa -in gwtools.ru.private -pubout &amp;gt; gwtools.ru.public&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DNS ====&lt;br /&gt;
===== MX =====&lt;br /&gt;
Для того, чтобы сервера знали, куда отправлять почту для домена, нужно добавить MX-запись.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; Я рекомендую добавлять запись на почтовый сервер именно как A-запись, а не CNAME (так же, как и для NS-записей):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    MX    10 mx1.gwtools.ru&lt;br /&gt;
mx1           A     1.2.3.4&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== SPF =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о том, с какого сервера будет отправляться почта и что делать с почтой с других адресов.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее о синтаксисе SPF можно прочитать на [https://yandex.ru/support/business/domains/dns/spf.html Яндексе] или [https://support.google.com/a/answer/10683907?hl=ru Google]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    TXT    &amp;quot;v=spf1 ip4:1.2.3.4 ~all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DKIM =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о публичном ключе:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mx1    TXT    &amp;quot;v=DKIM1; k=rsa; t=s; p=ABC...XYZ&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== ADSP =====&lt;br /&gt;
ADSP (Author Domain Signing Practices, http://tools.ietf.org/search/rfc5617 ) — практики использования DKIM в домене.&amp;lt;br&amp;gt;&lt;br /&gt;
ADSP это опциональное расширение DKIM, которое позволяет сообщать почтовым серверам-получателям, что делать с письмом пришедшим с якобы нашего домена, но не имеющее подписи.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_adsp    TXT    &amp;quot;dkim=all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DMARC ====&lt;br /&gt;
&lt;br /&gt;
==== Получение сертификатов ====&lt;br /&gt;
&lt;br /&gt;
==== Настройка сертификатов ====&lt;br /&gt;
&lt;br /&gt;
==== Отправка тестового письма с консоли ====&lt;br /&gt;
Воспользуемся telnet c почтового сервера:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
telnet localhost 25&lt;br /&gt;
HELO localhost&lt;br /&gt;
MAIL FROM:user@gwtools.ru&lt;br /&gt;
RCPT TO:user@ya.ru&lt;br /&gt;
DATA&lt;br /&gt;
SUBJECT:test subj&lt;br /&gt;
test msg&lt;br /&gt;
.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:FreeBSD]]&lt;br /&gt;
[[Category:Exim]]&lt;br /&gt;
[[Category:Mail]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=645</id>
		<title>Настройка Exim на FreeBSD</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=645"/>
		<updated>2023-07-01T16:52:24Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* Настройка ADSP */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Дано: FreeBSD 13, Exim 4.&lt;br /&gt;
Задача: развернуть почтовый relay, с поддержкой шифрования.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Исторически во FreeBSD используется sendmail. Он отлично выполняет свои функции, но в современном мире сложен в настройке, т.к. конфиг представляет собой набор сложночитаемых макросов.&lt;br /&gt;
&lt;br /&gt;
===== Установка exim =====&lt;br /&gt;
Воспользуемся пакетным менеджером:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg install exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После установки будет показана страница с дальнейшими шагами, которые надо выполнить.&amp;lt;br&amp;gt;&lt;br /&gt;
Если вы закрыли консоль, то найти эту информацию можно в файле &amp;lt;code&amp;gt;/usr/local/share/doc/exim/POST-INSTALL-NOTES&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== mailer.conf =====&lt;br /&gt;
Необходимо поменять sendmail на exim в файле &amp;lt;code&amp;gt;/etc/mail/mailer.conf&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Информацию для данного файла можно взять с оф. документации в пункте [https://www.exim.org/exim-html-current/doc/html/spec%20html/ch-building%20and%20installing%20exim.html 20. Replacing another MTA with Exim]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/exim/bin/exim&lt;br /&gt;
send-mail	/usr/exim/bin/exim&lt;br /&gt;
mailq		/usr/exim/bin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так же эту информацию можно взять из &amp;lt;code&amp;gt;man 5 mailer.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате я сделал общие изменения в таком виде:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
send-mail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение sendmail =====&lt;br /&gt;
В &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; добавляем строку &amp;lt;code&amp;gt;sendmail_enable=&amp;quot;NONE&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение заданий из расписания =====&lt;br /&gt;
Теперь нужно выключить скрипты, которые выполняются по расписанию.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; По умолчанию файла &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; в системе нет.&amp;lt;br&amp;gt;&lt;br /&gt;
Создаём его и добавляем следующее содержимое (всё из той же информации после установки):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
daily_clean_hoststat_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_status_include_submit_mailq=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_submit_queuerun=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_queuerun_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Включение exim =====&lt;br /&gt;
После добавления этой опции и редактирования конфига мы сможем запустить Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;exim_enable=&amp;quot;YES&amp;quot;&#039; &amp;gt; /etc/rc.conf.d/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Ротация логов =====&lt;br /&gt;
Создаём файл &amp;lt;code&amp;gt;/etc/newsyslog.conf.d/exim.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog   mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
/var/log/exim/rejectlog mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Настройка конфига =====&lt;br /&gt;
Открываем &amp;lt;code&amp;gt;/usr/local/etc/exim/configure&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для старта необходимо не так уж и много правок.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# primary_hostname = &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
По умолчанию опция закомментирована и информация берется из системы, но я бы рекомендовал активировать эту опцию.&amp;lt;br&amp;gt;&lt;br /&gt;
Указываем адрес сервера полностью, с доменом. Если сервер работает и на локальную сеть, и на интернет, то указываем существующий и доступный домен для интернета:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
primary_hostname = mx1.gwtools.ru&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = @&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Указываем адреса и домены с доступом к почте. Разделителем является двоеточие.&amp;lt;br&amp;gt;&lt;br /&gt;
Список local_domains содержит список обслуживаемых этим сервером доменов.&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_to_domains содержит список доменов, на которые можно отправлять почту без авторизации (проверка по получателю, например, на др. почтовый сервер).&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_from_hosts содержит список адресов или доменов, с которых разрешён доступ на отправку почты через данный сервер (например, локальная сеть).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = gwtools.ru&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost : 192.168.0.0/24&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Запуск exim =====&lt;br /&gt;
Данных изменений достаточно, чтобы запустить Exim и он мог получать и отправлять почту:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service exim start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DKIM ====&lt;br /&gt;
Переходим в папке с конфигами Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd /usr/local/etc/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем приватный ключ:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl genrsa -out gwtools.ru.private 1024&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем публичный ключ на основе приватного:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl rsa -in gwtools.ru.private -pubout &amp;gt; gwtools.ru.public&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DNS ====&lt;br /&gt;
===== MX =====&lt;br /&gt;
Для того, чтобы сервера знали, куда отправлять почту для домена, нужно добавить MX-запись.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; Я рекомендую добавлять запись на почтовый сервер именно как A-запись, а не CNAME (так же, как и для NS-записей):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    MX    10 mx1.gwtools.ru&lt;br /&gt;
mx1           A     1.2.3.4&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== SPF =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о том, с какого сервера будет отправляться почта и что делать с почтой с других адресов.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее о синтаксисе SPF можно прочитать на [https://yandex.ru/support/business/domains/dns/spf.html Яндексе] или [https://support.google.com/a/answer/10683907?hl=ru Google]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    TXT    &amp;quot;v=spf1 ip4:1.2.3.4 ~all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DKIM =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о публичном ключе:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mx1    TXT    &amp;quot;v=DKIM1; k=rsa; t=s; p=ABC...XYZ&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== ADSP ====&lt;br /&gt;
ADSP (Author Domain Signing Practices, http://tools.ietf.org/search/rfc5617 ) — практики использования DKIM в домене.&amp;lt;br&amp;gt;&lt;br /&gt;
ADSP это опциональное расширение DKIM, которое позволяет сообщать почтовым серверам-получателям, что делать с письмом пришедшим с якобы нашего домена, но не имеющее подписи.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_adsp    TXT    &amp;quot;dkim=all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DMARC ====&lt;br /&gt;
&lt;br /&gt;
==== Получение сертификатов ====&lt;br /&gt;
&lt;br /&gt;
==== Настройка сертификатов ====&lt;br /&gt;
&lt;br /&gt;
==== Отправка тестового письма с консоли ====&lt;br /&gt;
Воспользуемся telnet c почтового сервера:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
telnet localhost 25&lt;br /&gt;
HELO localhost&lt;br /&gt;
MAIL FROM:user@gwtools.ru&lt;br /&gt;
RCPT TO:user@ya.ru&lt;br /&gt;
DATA&lt;br /&gt;
SUBJECT:test subj&lt;br /&gt;
test msg&lt;br /&gt;
.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:FreeBSD]]&lt;br /&gt;
[[Category:Exim]]&lt;br /&gt;
[[Category:Mail]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=644</id>
		<title>Настройка Exim на FreeBSD</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=644"/>
		<updated>2023-07-01T16:52:18Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* DKIM */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Дано: FreeBSD 13, Exim 4.&lt;br /&gt;
Задача: развернуть почтовый relay, с поддержкой шифрования.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Исторически во FreeBSD используется sendmail. Он отлично выполняет свои функции, но в современном мире сложен в настройке, т.к. конфиг представляет собой набор сложночитаемых макросов.&lt;br /&gt;
&lt;br /&gt;
===== Установка exim =====&lt;br /&gt;
Воспользуемся пакетным менеджером:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg install exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После установки будет показана страница с дальнейшими шагами, которые надо выполнить.&amp;lt;br&amp;gt;&lt;br /&gt;
Если вы закрыли консоль, то найти эту информацию можно в файле &amp;lt;code&amp;gt;/usr/local/share/doc/exim/POST-INSTALL-NOTES&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== mailer.conf =====&lt;br /&gt;
Необходимо поменять sendmail на exim в файле &amp;lt;code&amp;gt;/etc/mail/mailer.conf&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Информацию для данного файла можно взять с оф. документации в пункте [https://www.exim.org/exim-html-current/doc/html/spec%20html/ch-building%20and%20installing%20exim.html 20. Replacing another MTA with Exim]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/exim/bin/exim&lt;br /&gt;
send-mail	/usr/exim/bin/exim&lt;br /&gt;
mailq		/usr/exim/bin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так же эту информацию можно взять из &amp;lt;code&amp;gt;man 5 mailer.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате я сделал общие изменения в таком виде:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
send-mail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение sendmail =====&lt;br /&gt;
В &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; добавляем строку &amp;lt;code&amp;gt;sendmail_enable=&amp;quot;NONE&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение заданий из расписания =====&lt;br /&gt;
Теперь нужно выключить скрипты, которые выполняются по расписанию.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; По умолчанию файла &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; в системе нет.&amp;lt;br&amp;gt;&lt;br /&gt;
Создаём его и добавляем следующее содержимое (всё из той же информации после установки):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
daily_clean_hoststat_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_status_include_submit_mailq=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_submit_queuerun=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_queuerun_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Включение exim =====&lt;br /&gt;
После добавления этой опции и редактирования конфига мы сможем запустить Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;exim_enable=&amp;quot;YES&amp;quot;&#039; &amp;gt; /etc/rc.conf.d/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Ротация логов =====&lt;br /&gt;
Создаём файл &amp;lt;code&amp;gt;/etc/newsyslog.conf.d/exim.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog   mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
/var/log/exim/rejectlog mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Настройка конфига =====&lt;br /&gt;
Открываем &amp;lt;code&amp;gt;/usr/local/etc/exim/configure&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для старта необходимо не так уж и много правок.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# primary_hostname = &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
По умолчанию опция закомментирована и информация берется из системы, но я бы рекомендовал активировать эту опцию.&amp;lt;br&amp;gt;&lt;br /&gt;
Указываем адрес сервера полностью, с доменом. Если сервер работает и на локальную сеть, и на интернет, то указываем существующий и доступный домен для интернета:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
primary_hostname = mx1.gwtools.ru&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = @&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Указываем адреса и домены с доступом к почте. Разделителем является двоеточие.&amp;lt;br&amp;gt;&lt;br /&gt;
Список local_domains содержит список обслуживаемых этим сервером доменов.&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_to_domains содержит список доменов, на которые можно отправлять почту без авторизации (проверка по получателю, например, на др. почтовый сервер).&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_from_hosts содержит список адресов или доменов, с которых разрешён доступ на отправку почты через данный сервер (например, локальная сеть).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = gwtools.ru&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost : 192.168.0.0/24&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Запуск exim =====&lt;br /&gt;
Данных изменений достаточно, чтобы запустить Exim и он мог получать и отправлять почту:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service exim start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DKIM ====&lt;br /&gt;
Переходим в папке с конфигами Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd /usr/local/etc/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем приватный ключ:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl genrsa -out gwtools.ru.private 1024&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем публичный ключ на основе приватного:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl rsa -in gwtools.ru.private -pubout &amp;gt; gwtools.ru.public&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DNS ====&lt;br /&gt;
===== MX =====&lt;br /&gt;
Для того, чтобы сервера знали, куда отправлять почту для домена, нужно добавить MX-запись.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; Я рекомендую добавлять запись на почтовый сервер именно как A-запись, а не CNAME (так же, как и для NS-записей):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    MX    10 mx1.gwtools.ru&lt;br /&gt;
mx1           A     1.2.3.4&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== SPF =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о том, с какого сервера будет отправляться почта и что делать с почтой с других адресов.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее о синтаксисе SPF можно прочитать на [https://yandex.ru/support/business/domains/dns/spf.html Яндексе] или [https://support.google.com/a/answer/10683907?hl=ru Google]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    TXT    &amp;quot;v=spf1 ip4:1.2.3.4 ~all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== DKIM =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о публичном ключе:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mx1    TXT    &amp;quot;v=DKIM1; k=rsa; t=s; p=ABC...XYZ&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка ADSP ====&lt;br /&gt;
ADSP (Author Domain Signing Practices, http://tools.ietf.org/search/rfc5617 ) — практики использования DKIM в домене.&amp;lt;br&amp;gt;&lt;br /&gt;
ADSP это опциональное расширение DKIM, которое позволяет сообщать почтовым серверам-получателям, что делать с письмом пришедшим с якобы нашего домена, но не имеющее подписи.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_adsp    TXT    &amp;quot;dkim=all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DMARC ====&lt;br /&gt;
&lt;br /&gt;
==== Получение сертификатов ====&lt;br /&gt;
&lt;br /&gt;
==== Настройка сертификатов ====&lt;br /&gt;
&lt;br /&gt;
==== Отправка тестового письма с консоли ====&lt;br /&gt;
Воспользуемся telnet c почтового сервера:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
telnet localhost 25&lt;br /&gt;
HELO localhost&lt;br /&gt;
MAIL FROM:user@gwtools.ru&lt;br /&gt;
RCPT TO:user@ya.ru&lt;br /&gt;
DATA&lt;br /&gt;
SUBJECT:test subj&lt;br /&gt;
test msg&lt;br /&gt;
.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:FreeBSD]]&lt;br /&gt;
[[Category:Exim]]&lt;br /&gt;
[[Category:Mail]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=643</id>
		<title>Настройка Exim на FreeBSD</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=643"/>
		<updated>2023-07-01T16:51:57Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* Запуск exim */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Дано: FreeBSD 13, Exim 4.&lt;br /&gt;
Задача: развернуть почтовый relay, с поддержкой шифрования.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Исторически во FreeBSD используется sendmail. Он отлично выполняет свои функции, но в современном мире сложен в настройке, т.к. конфиг представляет собой набор сложночитаемых макросов.&lt;br /&gt;
&lt;br /&gt;
===== Установка exim =====&lt;br /&gt;
Воспользуемся пакетным менеджером:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg install exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После установки будет показана страница с дальнейшими шагами, которые надо выполнить.&amp;lt;br&amp;gt;&lt;br /&gt;
Если вы закрыли консоль, то найти эту информацию можно в файле &amp;lt;code&amp;gt;/usr/local/share/doc/exim/POST-INSTALL-NOTES&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== mailer.conf =====&lt;br /&gt;
Необходимо поменять sendmail на exim в файле &amp;lt;code&amp;gt;/etc/mail/mailer.conf&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Информацию для данного файла можно взять с оф. документации в пункте [https://www.exim.org/exim-html-current/doc/html/spec%20html/ch-building%20and%20installing%20exim.html 20. Replacing another MTA with Exim]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/exim/bin/exim&lt;br /&gt;
send-mail	/usr/exim/bin/exim&lt;br /&gt;
mailq		/usr/exim/bin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так же эту информацию можно взять из &amp;lt;code&amp;gt;man 5 mailer.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате я сделал общие изменения в таком виде:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
send-mail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение sendmail =====&lt;br /&gt;
В &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; добавляем строку &amp;lt;code&amp;gt;sendmail_enable=&amp;quot;NONE&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение заданий из расписания =====&lt;br /&gt;
Теперь нужно выключить скрипты, которые выполняются по расписанию.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; По умолчанию файла &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; в системе нет.&amp;lt;br&amp;gt;&lt;br /&gt;
Создаём его и добавляем следующее содержимое (всё из той же информации после установки):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
daily_clean_hoststat_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_status_include_submit_mailq=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_submit_queuerun=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_queuerun_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Включение exim =====&lt;br /&gt;
После добавления этой опции и редактирования конфига мы сможем запустить Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;exim_enable=&amp;quot;YES&amp;quot;&#039; &amp;gt; /etc/rc.conf.d/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Ротация логов =====&lt;br /&gt;
Создаём файл &amp;lt;code&amp;gt;/etc/newsyslog.conf.d/exim.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog   mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
/var/log/exim/rejectlog mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Настройка конфига =====&lt;br /&gt;
Открываем &amp;lt;code&amp;gt;/usr/local/etc/exim/configure&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для старта необходимо не так уж и много правок.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# primary_hostname = &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
По умолчанию опция закомментирована и информация берется из системы, но я бы рекомендовал активировать эту опцию.&amp;lt;br&amp;gt;&lt;br /&gt;
Указываем адрес сервера полностью, с доменом. Если сервер работает и на локальную сеть, и на интернет, то указываем существующий и доступный домен для интернета:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
primary_hostname = mx1.gwtools.ru&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = @&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Указываем адреса и домены с доступом к почте. Разделителем является двоеточие.&amp;lt;br&amp;gt;&lt;br /&gt;
Список local_domains содержит список обслуживаемых этим сервером доменов.&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_to_domains содержит список доменов, на которые можно отправлять почту без авторизации (проверка по получателю, например, на др. почтовый сервер).&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_from_hosts содержит список адресов или доменов, с которых разрешён доступ на отправку почты через данный сервер (например, локальная сеть).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = gwtools.ru&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost : 192.168.0.0/24&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Запуск exim =====&lt;br /&gt;
Данных изменений достаточно, чтобы запустить Exim и он мог получать и отправлять почту:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service exim start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DKIM ====&lt;br /&gt;
Переходим в папке с конфигами Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
cd /usr/local/etc/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем приватный ключ:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl genrsa -out gwtools.ru.private 1024&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Генерируем публичный ключ на основе приватного:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
openssl rsa -in gwtools.ru.private -pubout &amp;gt; gwtools.ru.public&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DNS ====&lt;br /&gt;
===== MX =====&lt;br /&gt;
Для того, чтобы сервера знали, куда отправлять почту для домена, нужно добавить MX-запись.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; Я рекомендую добавлять запись на почтовый сервер именно как A-запись, а не CNAME (так же, как и для NS-записей):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    MX    10 mx1.gwtools.ru&lt;br /&gt;
mx1           A     1.2.3.4&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== SPF =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о том, с какого сервера будет отправляться почта и что делать с почтой с других адресов.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее о синтаксисе SPF можно прочитать на [https://yandex.ru/support/business/domains/dns/spf.html Яндексе] или [https://support.google.com/a/answer/10683907?hl=ru Google]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    TXT    &amp;quot;v=spf1 ip4:1.2.3.4 ~all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== DKIM ====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о публичном ключе:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mx1    TXT    &amp;quot;v=DKIM1; k=rsa; t=s; p=ABC...XYZ&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конфиге&lt;br /&gt;
&lt;br /&gt;
==== Настройка ADSP ====&lt;br /&gt;
ADSP (Author Domain Signing Practices, http://tools.ietf.org/search/rfc5617 ) — практики использования DKIM в домене.&amp;lt;br&amp;gt;&lt;br /&gt;
ADSP это опциональное расширение DKIM, которое позволяет сообщать почтовым серверам-получателям, что делать с письмом пришедшим с якобы нашего домена, но не имеющее подписи.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_adsp    TXT    &amp;quot;dkim=all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DMARC ====&lt;br /&gt;
&lt;br /&gt;
==== Получение сертификатов ====&lt;br /&gt;
&lt;br /&gt;
==== Настройка сертификатов ====&lt;br /&gt;
&lt;br /&gt;
==== Отправка тестового письма с консоли ====&lt;br /&gt;
Воспользуемся telnet c почтового сервера:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
telnet localhost 25&lt;br /&gt;
HELO localhost&lt;br /&gt;
MAIL FROM:user@gwtools.ru&lt;br /&gt;
RCPT TO:user@ya.ru&lt;br /&gt;
DATA&lt;br /&gt;
SUBJECT:test subj&lt;br /&gt;
test msg&lt;br /&gt;
.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:FreeBSD]]&lt;br /&gt;
[[Category:Exim]]&lt;br /&gt;
[[Category:Mail]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
	<entry>
		<id>https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=642</id>
		<title>Настройка Exim на FreeBSD</title>
		<link rel="alternate" type="text/html" href="https://wiki.gwtools.ru/index.php?title=%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0_Exim_%D0%BD%D0%B0_FreeBSD&amp;diff=642"/>
		<updated>2023-07-01T16:51:31Z</updated>

		<summary type="html">&lt;p&gt;Bas: /* Настройка DKIM */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Дано: FreeBSD 13, Exim 4.&lt;br /&gt;
Задача: развернуть почтовый relay, с поддержкой шифрования.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Исторически во FreeBSD используется sendmail. Он отлично выполняет свои функции, но в современном мире сложен в настройке, т.к. конфиг представляет собой набор сложночитаемых макросов.&lt;br /&gt;
&lt;br /&gt;
===== Установка exim =====&lt;br /&gt;
Воспользуемся пакетным менеджером:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pkg install exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
После установки будет показана страница с дальнейшими шагами, которые надо выполнить.&amp;lt;br&amp;gt;&lt;br /&gt;
Если вы закрыли консоль, то найти эту информацию можно в файле &amp;lt;code&amp;gt;/usr/local/share/doc/exim/POST-INSTALL-NOTES&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== mailer.conf =====&lt;br /&gt;
Необходимо поменять sendmail на exim в файле &amp;lt;code&amp;gt;/etc/mail/mailer.conf&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Информацию для данного файла можно взять с оф. документации в пункте [https://www.exim.org/exim-html-current/doc/html/spec%20html/ch-building%20and%20installing%20exim.html 20. Replacing another MTA with Exim]:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/exim/bin/exim&lt;br /&gt;
send-mail	/usr/exim/bin/exim&lt;br /&gt;
mailq		/usr/exim/bin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Так же эту информацию можно взять из &amp;lt;code&amp;gt;man 5 mailer.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В результате я сделал общие изменения в таком виде:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sendmail	/usr/local/sbin/exim&lt;br /&gt;
send-mail	/usr/local/sbin/exim&lt;br /&gt;
mailq		/usr/local/sbin/exim -bp&lt;br /&gt;
newaliases	/usr/bin/true&lt;br /&gt;
rmail		/usr/local/sbin/exim -i -oee&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение sendmail =====&lt;br /&gt;
В &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; добавляем строку &amp;lt;code&amp;gt;sendmail_enable=&amp;quot;NONE&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Выключение заданий из расписания =====&lt;br /&gt;
Теперь нужно выключить скрипты, которые выполняются по расписанию.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; По умолчанию файла &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; в системе нет.&amp;lt;br&amp;gt;&lt;br /&gt;
Создаём его и добавляем следующее содержимое (всё из той же информации после установки):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
daily_clean_hoststat_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_status_include_submit_mailq=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_submit_queuerun=&amp;quot;NO&amp;quot;&lt;br /&gt;
daily_queuerun_enable=&amp;quot;NO&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Включение exim =====&lt;br /&gt;
После добавления этой опции и редактирования конфига мы сможем запустить Exim:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &#039;exim_enable=&amp;quot;YES&amp;quot;&#039; &amp;gt; /etc/rc.conf.d/exim&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Ротация логов =====&lt;br /&gt;
Создаём файл &amp;lt;code&amp;gt;/etc/newsyslog.conf.d/exim.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/var/log/exim/mainlog   mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
/var/log/exim/rejectlog mailnull:mail 640 7 * @T00 ZN&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Настройка конфига =====&lt;br /&gt;
Открываем &amp;lt;code&amp;gt;/usr/local/etc/exim/configure&amp;lt;/code&amp;gt;.&amp;lt;br&amp;gt;&lt;br /&gt;
Для старта необходимо не так уж и много правок.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# primary_hostname = &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
По умолчанию опция закомментирована и информация берется из системы, но я бы рекомендовал активировать эту опцию.&amp;lt;br&amp;gt;&lt;br /&gt;
Указываем адрес сервера полностью, с доменом. Если сервер работает и на локальную сеть, и на интернет, то указываем существующий и доступный домен для интернета:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
primary_hostname = mx1.gwtools.ru&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = @&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Указываем адреса и домены с доступом к почте. Разделителем является двоеточие.&amp;lt;br&amp;gt;&lt;br /&gt;
Список local_domains содержит список обслуживаемых этим сервером доменов.&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_to_domains содержит список доменов, на которые можно отправлять почту без авторизации (проверка по получателю, например, на др. почтовый сервер).&amp;lt;br&amp;gt;&lt;br /&gt;
Список relay_from_hosts содержит список адресов или доменов, с которых разрешён доступ на отправку почты через данный сервер (например, локальная сеть).&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
domainlist local_domains = gwtools.ru&lt;br /&gt;
domainlist relay_to_domains =&lt;br /&gt;
hostlist   relay_from_hosts = localhost : 192.168.0.0/24&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Запуск exim =====&lt;br /&gt;
Данных изменений достаточно, чтобы запустить Exim и он мог получать и отправлять почту:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
service exim start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DNS ====&lt;br /&gt;
===== MX =====&lt;br /&gt;
Для того, чтобы сервера знали, куда отправлять почту для домена, нужно добавить MX-запись.&amp;lt;br&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Важно!&#039;&#039;&#039; Я рекомендую добавлять запись на почтовый сервер именно как A-запись, а не CNAME (так же, как и для NS-записей):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    MX    10 mx1.gwtools.ru&lt;br /&gt;
mx1           A     1.2.3.4&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== SPF =====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о том, с какого сервера будет отправляться почта и что делать с почтой с других адресов.&amp;lt;br&amp;gt;&lt;br /&gt;
Подробнее о синтаксисе SPF можно прочитать на [https://yandex.ru/support/business/domains/dns/spf.html Яндексе] или [https://support.google.com/a/answer/10683907?hl=ru Google]&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
gwtools.ru    TXT    &amp;quot;v=spf1 ip4:1.2.3.4 ~all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== DKIM ====&lt;br /&gt;
Добавляем в DNS-зону нашего домена информацию о публичном ключе:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mx1    TXT    &amp;quot;v=DKIM1; k=rsa; t=s; p=ABC...XYZ&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
В конфиге&lt;br /&gt;
&lt;br /&gt;
==== Настройка ADSP ====&lt;br /&gt;
ADSP (Author Domain Signing Practices, http://tools.ietf.org/search/rfc5617 ) — практики использования DKIM в домене.&amp;lt;br&amp;gt;&lt;br /&gt;
ADSP это опциональное расширение DKIM, которое позволяет сообщать почтовым серверам-получателям, что делать с письмом пришедшим с якобы нашего домена, но не имеющее подписи.&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
_adsp    TXT    &amp;quot;dkim=all&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Настройка DMARC ====&lt;br /&gt;
&lt;br /&gt;
==== Получение сертификатов ====&lt;br /&gt;
&lt;br /&gt;
==== Настройка сертификатов ====&lt;br /&gt;
&lt;br /&gt;
==== Отправка тестового письма с консоли ====&lt;br /&gt;
Воспользуемся telnet c почтового сервера:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
telnet localhost 25&lt;br /&gt;
HELO localhost&lt;br /&gt;
MAIL FROM:user@gwtools.ru&lt;br /&gt;
RCPT TO:user@ya.ru&lt;br /&gt;
DATA&lt;br /&gt;
SUBJECT:test subj&lt;br /&gt;
test msg&lt;br /&gt;
.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:FreeBSD]]&lt;br /&gt;
[[Category:Exim]]&lt;br /&gt;
[[Category:Mail]]&lt;/div&gt;</summary>
		<author><name>Bas</name></author>
	</entry>
</feed>