Zimbra: различия между версиями

Материал из wiki.nntc.nnov.ru
Перейти к навигации Перейти к поиску
(Переименовать)
(Интеграция)
 
(не показано 6 промежуточных версий этого же участника)
Строка 355: Строка 355:
 
EOF
 
EOF
 
</pre>
 
</pre>
 +
 +
 +
=Интеграция=
 +
 +
В этом разделе представлен пример небольшого сервера для интеграции с zimbra. Сервер представляет из себя HTTP сервис, принимающий POST запрос с логином и паролем от внешней системы.
 +
 +
Сервер написан на nodejs, поэтому для его функционирования необходимо поставить пакет nodejs
 +
 +
apt-get install nodejs
 +
 +
Общий смысл работы сервера:
 +
 +
1. Принять POST запрос от внешней системы
 +
 +
2. Распознать в запросе корректно присланные логин и пароль
 +
 +
3. Добавить к логину домен почтового аккаунта (настраивается в файле скрипта /opt/zimbra_sync/exec.sh)
 +
 +
4. Проверить существования аккаунта в zimbra средствами zmprov
 +
 +
5. Если аккаунт существует, то поменять ему пароль
 +
 +
6. Если аккаунт не существует, то создать новый аккаунт и установить для него пароль
 +
 +
Сервер должен запускаться на хосте с zimbra под суперпользователем.
 +
 +
Пример внешней системы, которая может быть клиентом описанного в этом разделе сервера, описан [[ Pam#.D0.97.D0.B0.D0.BF.D1.83.D1.81.D1.82.D0.B8.D1.82.D1.8C_.D1.81.D1.86.D0.B5.D0.BD.D0.B0.D1.80.D0.B8.D0.B9_.D0.BF.D1.80.D0.B8_.D0.B2.D1.85.D0.BE.D0.B4.D0.B5_.D0.BF.D0.BE.D0.BB.D1.8C.D0.B7.D0.BE.D0.B2.D0.B0.D1.82.D0.B5.D0.BB.D1.8F_.D0.B2_GNU.2FLinux_.D1.81_.D0.BF.D0.B5.D1.80.D0.B5.D0.B4.D0.B0.D1.87.D0.B5.D0.B9_.D0.B2_.D0.BD.D0.B5.D0.B3.D0.BE_.D0.BB.D0.BE.D0.B3.D0.B8.D0.BD.D0.B0_.D0.B8_.D0.BF.D0.B0.D1.80.D0.BE.D0.BB.D1.8F | здесь]]
 +
 +
==Создаём каталог для сервера интеграции==
 +
 +
mkdir /opt/zimbra_sync
 +
 +
==Создаём файлы сервера на nodejs и скрипт, который будет выполняться при обращении к серверу==
 +
 +
===Сам сервер===
 +
 +
<pre>
 +
cat << 'EOF' > /opt/zimbra_sync/app.js
 +
const http = require('http');
 +
const spawn = require('child_process').spawn;
 +
const path = require('path');
 +
 +
const server = http.createServer((req, res) => {
 +
  if (req.method === 'POST' && req.url === '/') {
 +
    let data = '';
 +
 +
    req.on('data', (chunk) => {
 +
      data += chunk;
 +
    });
 +
 +
    req.on('end', () => {
 +
      try {
 +
        const { login, password } = JSON.parse(data);
 +
 +
        if (!login || !password) {
 +
          res.writeHead(400, { 'Content-Type': 'application/json' });
 +
          res.end(JSON.stringify({ error: 'Missing login or password in the request body.' }));
 +
          return;
 +
        }
 +
 +
const scriptPath=`/opt/zimbra_sync/exec.sh`;
 +
 +
        const process = spawn(scriptPath, [login, password]);
 +
 +
        process.on('close', (code) => {
 +
          res.writeHead(200, { 'Content-Type': 'application/json' });
 +
          res.end(JSON.stringify({ exitCode: code }));
 +
        });
 +
 +
        process.on('error', (error) => {
 +
          res.writeHead(500, { 'Content-Type': 'application/json' });
 +
          res.end(JSON.stringify({ error: `Error while executing the process: ${error.message}` }));
 +
        });
 +
 +
      } catch (error) {
 +
        res.writeHead(400, { 'Content-Type': 'application/json' });
 +
        res.end(JSON.stringify(error));
 +
      }
 +
    });
 +
  } else {
 +
    res.writeHead(404, { 'Content-Type': 'application/json' });
 +
    res.end(JSON.stringify({ error: 'Not Found' }));
 +
  }
 +
});
 +
 +
const port = 65432;
 +
const host = '0.0.0.0';
 +
 +
server.listen(port, host, () => {
 +
  console.log(`zimbra_sync server listen http://${host}:${port}`);
 +
});
 +
EOF
 +
</pre>
 +
 +
===Скрипт===
 +
 +
<pre>
 +
cat << 'EOF' > /opt/zimbra_sync/exec.sh
 +
#!/bin/bash
 +
login="$1"
 +
password="$2"
 +
email_domain='yourdomain.com'
 +
 +
function is_account_exist(){
 +
su - zimbra -c "zmprov GetAccount \"${1}\" > /dev/null"
 +
echo $?
 +
}
 +
 +
mailbox="${login}@${email_domain}"
 +
 +
check=`is_account_exist "${mailbox}"`
 +
 +
if [ $check -eq 0 ]; then
 +
# set password
 +
echo "update password for exist account"
 +
su - zimbra -c "zmprov sp ${mailbox} ${password}";
 +
su - zimbra -c "zmprov ma ${mailbox} zimbraPasswordMustChange FALSE";
 +
else
 +
echo "create account and set password"
 +
# create account
 +
su - zimbra -c "zmprov ca ${mailbox} ${password}";
 +
# set password
 +
su - zimbra -c "zmprov sp ${mailbox} ${password}";
 +
su - zimbra -c "zmprov ma ${mailbox} zimbraPasswordMustChange FALSE";
 +
fi
 +
 +
exit 0
 +
EOF
 +
</pre>
 +
 +
chmod +x /opt/zimbra_sync/exec.sh
 +
 +
==Создаём сервис systemd и включаем его==
 +
 +
<pre>
 +
cat << 'EOF' > /etc/systemd/system/zimbra_sync.service
 +
[Unit]
 +
Description=zimbra_sync
 +
Requires=network-online.target
 +
After=network-online.target
 +
[Service]
 +
Restart=always
 +
RestartSec=3
 +
WorkingDirectory=/opt/zimbra_sync/
 +
ExecStart=/usr/bin/node app.js
 +
User=root
 +
Group=root
 +
[Install]
 +
WantedBy=multi-user.target
 +
EOF
 +
</pre>
 +
 +
systemctl daemon-reload
 +
 +
systemctl enable --now zimbra_sync
 +
 +
=Установка сертификатов=
 +
 +
https://habr.com/ru/companies/Zextras/articles/341996/

Текущая версия на 15:39, 31 октября 2024

Zimbra
Почтовый сервер


URL: https://mail.nntc.nnov.ru
Платформа: Zimbra
Сервер: apve3/vestacp
IP-адрес: 192.168.10.151
Открытые порты: 443TCP (Web-интерфейс)

Настройка доступа к контактам через Thunderbird

Данная инструкция проверялась на Ubuntu 20.04.

  • Скачать и установить дополнение CardBook в Thunderbird. Можно найти через менеджер дополнений и установить оттуда же.
  • Открыть окно CardBook (кнопка находится в правом верхнем углу окна Thunderbird.)
  • Правой кнопкой мыши кликнуть на список адресных книг в левой части экрана и выбрать из меню "Новая адресная книга" ("New Address Book".)
  • В открывшемся окне выбрат тип адресной книги "Remote".
  • Выбрать тип адресной книги "CardDAV". В настройках подключения указать URL следующего вида:
 https://mail.nntc.nnov.ru/dav/<user-name>@nntc.nnov.ru/Contacts/
  • Ввести имя пользователя почты и пароль.


bind

cat << 'EOF' > /etc/bind/named.conf.options 
options {
	directory "/var/cache/bind";

	// forward only;

	// If there is a firewall between you and nameservers you want
	// to talk to, you may need to fix the firewall to allow multiple
	// ports to talk.  See http://www.kb.cert.org/vuls/id/800113

	// If your ISP provided one or more IP addresses for stable 
	// nameservers, you probably want to use them as forwarders.  
	// Uncomment the following block, and insert the addresses replacing 
	// the all-0's placeholder.

	forwarders {
	  8.8.8.8;
	};

	//========================================================================
	// If BIND logs error messages about the root key being expired,
	// you will need to update your keys.  See https://www.isc.org/bind-keys
	//========================================================================
	dnssec-validation no;

	listen-on-v6 { any; };
};
EOF
echo 'include "/etc/bind/qwe.rty.ru_zones.conf";' >> /etc/bind/named.conf


cat << 'EOF' > /etc/bind/qwe.rty.ru_zones.conf 
zone "qwe.rty.ru" {
    type master;
    file "/etc/bind/zone_qwe.rty.ru";
};
EOF


cat << 'EOF' > /etc/bind/zone_qwe.rty.ru 
$ORIGIN qwe.rty.ru.
$TTL 86400
@ IN SOA ns1.qwe.rty.ru. root.qwe.rty.ru. (
2023013110; Serial
3600 ; Refresh
900 ; Retry
604800 ; Expire
86400 ; Minimum
)
;
	IN	NS	ns1.qwe.rty.ru.
@	IN	A	192.168.10.51
ns1	IN	A	192.168.10.51
ns2	IN	A	192.168.10.51
www	IN	CNAME	@
;mail	IN	CNAME	@
mail	IN	A	192.168.10.51
@	IN	MX 10	mail
mail	IN	MX 10	mail
@	IN	TXT	"v=spf1 a mx ip4:89.109.54.20 ~all"
_dmarc	IN	TXT	"v=DMARC1; p=quarantine"
2B2943B4-A15F-11ED-BFE7-22BC155843E7._domainkey 300 IN TXT (
"v=DKIM1; k=rsa;"
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoGbExU/BhOyVqPnwz80o1NKVTkH9zPvo4x6Qs5+MuZsWH+fk5lpKxtrhiD+3+sjzpMZBSXmJ5JEefqW+0dByQrATJY91ynYzY3hOFdhLvkptNisNiycdFT5u3lT4PxfXG/GB8CVvX88OXXjXuD3zAL2cbs+u+AsY2a+85D/5L1/SPxqvUOi1B1o0Lsk9e4shZTLlIH/0NKF9yE"
"3ECAsAJiHhV3GJZbytTBR8lAHb7UCBgqI1/2tOB2gaCfXKOoJ4shcCEjrlzGT6C6c7I7J0i4UMRgv446EMK7/Ddynmu7SisiOXnl06J38GKtLp36PHtOZYhOJWc0ke0ap9+7YQMQIDAQAB"
)
EOF
systemctl stop systemd-resolved
systemctl disable systemd-resolved

Настройка dkim, spf, dmark

Источник: https://habr.com/ru/company/Zextras/blog/339296/

dkim

Добавляем dkim к домену

/opt/zimbra/libexec/zmdkimkeyutil -a -d nntc.nnov.ru

В ответ получаем текст типа такого

DKIM Data added to LDAP for domain nntc.nnov.ru with selector 1774E6C4-60E2-11ED-9312-806B2F1CB351
Public signature to enter into DNS:
1774E6C4-60E2-11ED-9312-806B2F1CB351._domainkey	IN	TXT	( "v=DKIM1; k=rsa; "
	  "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvoJiPVvchkOXgMRcxHTMTlkmFIk5BLkHqbY7htxDCRD7Pnwchktcdg7HAWd7SMgdJeSylo221GKKrx/f1nOYp0naBcRzPXi9R3a/a3fsY94indUuI1JnwrL3RCoCl4TkTSAP4VGP9hcTcf1ms6u2mbKuiKgdaKeXieXInIhg+KasYm9hnFTMX8I+mZOjvwbOQETpmxrXUW8Uo9"
	  "I87BJDBMF5Wb2u6Mr0KaoCjpie9Y89YwXGID6Ofor1Rg2qra507oNIPnFFhKI6KacBMS1kQTESgJ0AJxvyjuc+fKGcmA+sh2rzYLlRulvGu4mBsZkKJuLP0iuZgymPS1oB0PA5WQIDAQAB" )  ; ----- DKIM key 1774E6C4-60E2-11ED-9312-806B2F1CB351 for nntc.nnov.ru

Далее идём в настройки DNS записей для домена nntc.nnov.ru и добавляем такую TXT запись в настройках домена.

В случае с настройками днс сервера типа bind9 запись будет выглядеть примерно так. Если интерфейс настройки другой, то бездумно копировать этот текст не надо. Надо копировать - подумав! :-)

1774E6C4-60E2-11ED-9312-806B2F1CB351._domainkey	IN	TXT	( "v=DKIM1; k=rsa; "
	  "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvoJiPVvchkOXgMRcxHTMTlkmFIk5BLkHqbY7htxDCRD7Pnwchktcdg7HAWd7SMgdJeSylo221GKKrx/f1nOYp0naBcRzPXi9R3a/a3fsY94indUuI1JnwrL3RCoCl4TkTSAP4VGP9hcTcf1ms6u2mbKuiKgdaKeXieXInIhg+KasYm9hnFTMX8I+mZOjvwbOQETpmxrXUW8Uo9"
	  "I87BJDBMF5Wb2u6Mr0KaoCjpie9Y89YwXGID6Ofor1Rg2qra507oNIPnFFhKI6KacBMS1kQTESgJ0AJxvyjuc+fKGcmA+sh2rzYLlRulvGu4mBsZkKJuLP0iuZgymPS1oB0PA5WQIDAQAB" )  ; ----- DKIM key 1774E6C4-60E2-11ED-9312-806B2F1CB351 for nntc.nnov.ru


Проверяем

host -t txt 1774E6C4-60E2-11ED-9312-806B2F1CB351._domainkey.nntc.nnov.ru

В ответ должен прийти тот текст, который является значением для TXT зоны 1774E6C4-60E2-11ED-9312-806B2F1CB351._domainkey.nntc.nnov.ru, добавленной выше где-то там на сервере, который обслуживает днс зону


Формируем команду для проверки ключа в зимбре.

/opt/zimbra/common/sbin/opendkim-testkey -d nntc.nnov.ru -s 1774E6C4-60E2-11ED-9312-806B2F1CB351 -x /opt/zimbra/conf/opendkim.conf

Если в ответ не прилетает текста с ошибками, значит всё настроено верно, скорее всего. Но нужно убедиться, выполнив проверку на стороннем сервисе. Например здесь: https://www.mail-tester.com

Ещё важный момент: Если на самой зимбре настроен внутренний днс сервер на bind9, то в зону nntc.nnov.ru необходимо также добавить выше описанную TXT запись.

spf

В нашем случае для spf нужно сделать такую TXT запись для домена nntc.nnov.ru

v=spf1 a mx ip4:89.109.54.20 ~all

dmark

В нашем случае для dmark нужно сделать такую TXT запись для домена _dmarc.nntc.nnov.ru

v=DMARC1; p=quarantine

Итог

В итоге зона DNS сервера должна иметь такие настройки, которые представлены ниже в формате конфига для сервера bind9

$ORIGIN nntc.nnov.ru.
$TTL 86400
@ IN SOA ns1.nntc.nnov.ru. root.nntc.nnov.ru. (
2022111004 ; Serial
3600 ; Refresh
900 ; Retry
604800 ; Expire
86400 ; Minimum
)
;
	IN	NS	ns1.nntc.nnov.ru.
@	IN	A	192.168.10.51
ns1	IN	A	192.168.10.51
ns2	IN	A	192.168.10.51
www	IN	CNAME	@
;mail	IN	CNAME	@
mail	IN	A	192.168.10.51
@	IN	MX 10	mail
mail	IN	MX 10	mail
@	IN	TXT	"v=spf1 a mx ip4:89.109.54.20 ~all"
_dmarc	IN	TXT	"v=DMARC1; p=quarantine"
1774E6C4-60E2-11ED-9312-806B2F1CB351._domainkey	IN	TXT	( "v=DKIM1; k=rsa; "
	  "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvoJiPVvchkOXgMRcxHTMTlkmFIk5BLkHqbY7htxDCRD7Pnwchktcdg7HAWd7SMgdJeSylo221GKKrx/f1nOYp0naBcRzPXi9R3a/a3fsY94indUuI1JnwrL3RCoCl4TkTSAP4VGP9hcTcf1ms6u2mbKuiKgdaKeXieXInIhg+KasYm9hnFTMX8I+mZOjvwbOQETpmxrXUW8Uo9"
	  "I87BJDBMF5Wb2u6Mr0KaoCjpie9Y89YwXGID6Ofor1Rg2qra507oNIPnFFhKI6KacBMS1kQTESgJ0AJxvyjuc+fKGcmA+sh2rzYLlRulvGu4mBsZkKJuLP0iuZgymPS1oB0PA5WQIDAQAB" )  ; ----- DKIM key 1774E6C4-60E2-11ED-9312-806B2F1CB351 for nntc.nnov.ru

Фиксы

Переименовать

su - zimbra
/opt/zimbra/libexec/zmsetservername -n nntc.nnov.ru

Предотвратить попадания писем в спам для внутренних ящиков

zmprov ms `zmhostname` zimbraAmavisOriginatingBypassSA TRUE
zmamavisdctl restart

Создать spam,ham and quarantine accounts

http://docs.zimbra.com/docs/os/6.0.2/administration_guide/A_app-command-line.13.3.html

Get the previous account names from the global configuration:

su - zimbra
zmprov -l gacf zimbraAmavisQuarantineAccount zimbraSpamIsSpamAccount zimbraSpamIsNotSpamAccount

Now recreate the account using the name obtained in the previous step: Spam Account:

zmprov ca <spam-account@example.com> <PASSWORD> amavisBypassSpamChecks TRUE zimbraAttachmentsIndexingEnabled FALSE zimbraIsSystemAccount TRUE zimbraIsSystemResource TRUE zimbraHideInGal TRUE description 'System account for spam training.'

Ham Account:

zmprov ca <ham-account@example.com> <PASSWORD> amavisBypassSpamChecks TRUE zimbraAttachmentsIndexingEnabled FALSE zimbraIsSystemAccount TRUE zimbraIsSystemResource TRUE zimbraHideInGal TRUE description 'System account for Non-Spam (Ham) training.'

Quarantine Account:

zmprov ca <virus-quarantine-account@example.com> <PASSWORD> amavisBypassSpamChecks TRUE zimbraAttachmentsIndexingEnabled FALSE zimbraIsSystemAccount TRUE zimbraIsSystemResource TRUE zimbraHideInGal TRUE zimbraMailMessageLifetime 30d description 'System account for Anti-virus quarantine.'


Steps to create new spam, ham and quarantine accounts

apt install binutils

The above recreates existing accounts. If there is a need to recreate accounts with a random name, like spam.<random number> and ham.<random number>, follow these steps.

First create spam, ham and quarantine accounts with random passwords and add a random string in account name.:

zmprov ca spam.`strings /dev/urandom | tr -dc _A-Z-a-z-0-9 | head -c10`@example.com "`strings /dev/urandom | tr -dc _A-Z-a-z-0-9 | head -c10`" amavisBypassSpamChecks TRUE zimbraAttachmentsIndexingEnabled FALSE zimbraIsSystemAccount TRUE zimbraIsSystemResource TRUE zimbraHideInGal TRUE description 'System account for spam training.'


zmprov ca ham.`strings /dev/urandom | tr -dc _A-Z-a-z-0-9 | head -c10`@example.com "`strings /dev/urandom | tr -dc _A-Z-a-z-0-9 | head -c10`" amavisBypassSpamChecks TRUE zimbraAttachmentsIndexingEnabled FALSE zimbraIsSystemAccount TRUE zimbraIsSystemResource TRUE zimbraHideInGal TRUE description 'System account for Non-Spam (Ham) training.'


zmprov ca virus-quarantine.`strings /dev/urandom | tr -dc _A-Z-a-z-0-9 | head -c10`@example.com "`strings /dev/urandom | tr -dc _A-Z-a-z-0-9 | head -c10`" amavisBypassSpamChecks TRUE zimbraAttachmentsIndexingEnabled FALSE zimbraIsSystemAccount TRUE zimbraIsSystemResource TRUE zimbraHideInGal TRUE zimbraMailMessageLifetime 30d description 'System account for Anti-virus quarantine.'

Get the new account names:

zmprov -l gaa | egrep -i 'spam|ham|virus-quarantine'

Now add these to the global configuration, and restart for the changes to take effect:

zmprov mcf zimbraSpamIsSpamAccount <spam-account@example.com> zimbraSpamIsNotSpamAccount <ham-account@example.com> zimbraAmavisQuarantineAccount <virus-quarantine-account@example.com>
zmcontrol restart

Note: Replace example.com with the actual primary domain name.

Улучшить параметры получения писем

https://habr.com/ru/company/Zextras/blog/436304/

В файле

/opt/zimbra/conf/opendkim.conf.in

выставить параметры

On-NoSignature reject
Mode sv

перезапустить сервис

su - zimbra
zmopendkimctl restart

Скрипты

Получить список ящиков в файл

su zimbra -
zmaccts | grep "@" | awk '{print $1}' > accounts.txt

Резервное копирование

cat << 'EOF' > /opt/export-boxes.sh
#!/bin/bash

dest="/mnt/backup"

for i in $(cat accounts.txt); do

mailbox="$i"
stamp="$(date +%Y_%m_%d__%H_%M_%S)"
destfile="${dest}/${mailbox}_${stamp}".tar


echo "now backup mbox $mailbox to $destfile"

/opt/zimbra/bin/zmmailbox -z -t 0 -m "${mailbox}" getRestURL -u "https://mail.nntc.nnov.ru" "//?fmt=tar" > ${destfile}
echo "done. Next..."
echo ""

done
EOF

Восстановление из резервных копий

cat << 'EOF' > /opt/import-boxes.sh
#!/bin/bash

dest="/mnt/backup"

for i in $(cat accounts.txt); do

mailbox="$i"
stamp="$(date +%Y_%m_%d__%H_%M_%S)"
destfile="${dest}/${mailbox}_${stamp}".tar


echo "now backup mbox $mailbox to $destfile"

/opt/zimbra/bin/zmmailbox -z -t 0 -m "${mailbox}" getRestURL -u "https://mail.nntc.nnov.ru" "//?fmt=tar" > ${destfile}
echo "done. Next..."
echo ""

done
EOF

Сменить всем пароли

cat << 'EOF' > /opt/passwd-boxes.sh
#!/bin/bash

out="accounts.pwd"

rm ${out}

for ACCOUNT in `cat accounts.txt`; do

PASS=`pwgen -s 12 1`
echo $ACCOUNT
#su - zimbra -c "zmprov sp $ACCOUNT NewGenerated42Password";
#su - zimbra -c "zmprov ma $ACCOUNT zimbraPasswordMustChange TRUE";

echo "${ACCOUNT} ${PASS}" >> ${out}

done

exit 0
EOF


Интеграция

В этом разделе представлен пример небольшого сервера для интеграции с zimbra. Сервер представляет из себя HTTP сервис, принимающий POST запрос с логином и паролем от внешней системы.

Сервер написан на nodejs, поэтому для его функционирования необходимо поставить пакет nodejs

apt-get install nodejs

Общий смысл работы сервера:

1. Принять POST запрос от внешней системы

2. Распознать в запросе корректно присланные логин и пароль

3. Добавить к логину домен почтового аккаунта (настраивается в файле скрипта /opt/zimbra_sync/exec.sh)

4. Проверить существования аккаунта в zimbra средствами zmprov

5. Если аккаунт существует, то поменять ему пароль

6. Если аккаунт не существует, то создать новый аккаунт и установить для него пароль

Сервер должен запускаться на хосте с zimbra под суперпользователем.

Пример внешней системы, которая может быть клиентом описанного в этом разделе сервера, описан здесь

Создаём каталог для сервера интеграции

mkdir /opt/zimbra_sync

Создаём файлы сервера на nodejs и скрипт, который будет выполняться при обращении к серверу

Сам сервер

cat << 'EOF' > /opt/zimbra_sync/app.js 
const http = require('http');
const spawn = require('child_process').spawn;
const path = require('path');

const server = http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/') {
    let data = '';

    req.on('data', (chunk) => {
      data += chunk;
    });

    req.on('end', () => {
      try {
        const { login, password } = JSON.parse(data);

        if (!login || !password) {
          res.writeHead(400, { 'Content-Type': 'application/json' });
          res.end(JSON.stringify({ error: 'Missing login or password in the request body.' }));
          return;
        }

	const scriptPath=`/opt/zimbra_sync/exec.sh`;

        const process = spawn(scriptPath, [login, password]);

        process.on('close', (code) => {
          res.writeHead(200, { 'Content-Type': 'application/json' });
          res.end(JSON.stringify({ exitCode: code }));
        });

        process.on('error', (error) => {
          res.writeHead(500, { 'Content-Type': 'application/json' });
          res.end(JSON.stringify({ error: `Error while executing the process: ${error.message}` }));
        });

      } catch (error) {
        res.writeHead(400, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(error));
      }
    });
  } else {
    res.writeHead(404, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'Not Found' }));
  }
});

const port = 65432;
const host = '0.0.0.0';

server.listen(port, host, () => {
  console.log(`zimbra_sync server listen http://${host}:${port}`);
});
EOF

Скрипт

cat << 'EOF' > /opt/zimbra_sync/exec.sh 
#!/bin/bash
login="$1"
password="$2"
email_domain='yourdomain.com'

function is_account_exist(){
su - zimbra -c "zmprov GetAccount \"${1}\" > /dev/null"
echo $?
}

mailbox="${login}@${email_domain}"

check=`is_account_exist "${mailbox}"`

if [ $check -eq 0 ]; then
# set password
echo "update password for exist account"
su - zimbra -c "zmprov sp ${mailbox} ${password}";
su - zimbra -c "zmprov ma ${mailbox} zimbraPasswordMustChange FALSE";
else
echo "create account and set password"
# create account
su - zimbra -c "zmprov ca ${mailbox} ${password}";
# set password
su - zimbra -c "zmprov sp ${mailbox} ${password}";
su - zimbra -c "zmprov ma ${mailbox} zimbraPasswordMustChange FALSE";
fi

exit 0
EOF
chmod +x /opt/zimbra_sync/exec.sh

Создаём сервис systemd и включаем его

cat << 'EOF' > /etc/systemd/system/zimbra_sync.service 
[Unit]
Description=zimbra_sync
Requires=network-online.target
After=network-online.target
[Service]
Restart=always
RestartSec=3
WorkingDirectory=/opt/zimbra_sync/
ExecStart=/usr/bin/node app.js
User=root
Group=root
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now zimbra_sync

Установка сертификатов

https://habr.com/ru/companies/Zextras/articles/341996/