Данная статья о том как настроить современный почтовый сервер.
Postfix + Dovecot. SPF + DKIM + rDNS. С IPv6.
С шифрованием TSL. С поддержкой нескольких доменов — часть с настоящим SSL сертификатом.
С антиспам-защитой и высоким антиспам-рейтингом у других почтовых серверов.
С поддержкой нескольких физических интерфейсов.
С OpenVPN, подключение к которому через IPv4, и которое даёт IPv6.

Если вы не хотите изучать эти все технологии, но хотите настроить такой сервер — тогда эта статья для вас.

В статье отсутствуют попытки пояснить каждую деталь. Пояснение идёт к тому, что настроено не стандартно или важно с точки зрения потребителя.

Мотивация настроить почтовый сервер — моя давняя мечта. Может это звучит глупо, но ИМХО, это гораздо лучше, чем мечтать о новой машине любимой марки.

Мотивация настроить IPv6 — две. ИТ специалисту необходимо изучать новые технологии постоянно, чтобы выжить. Хочется внести свой скромный вклад в борьбу с цензурой.

Мотивация настройки OpenVPN — только для того, чтобы IPv6 работал на локальной машине.
Мотивация настройки нескольких физических интерфейсов — у меня на сервере один интерфейс «медленный, но безлимитный», а другой «быстрый, но с тарифом».

Мотивация настройки настройки Bind — мой провайдер предоставляет нестабильный DNS сервер, а google бывает тоже даёт сбои. Хочу стабильный DNS сервер для личного использования.

Мотивация написать статью — черновик был написал 10 месяцев назад, и я в него уже два раза заглядывал. Если даже автору это регулярно надо — то большая вероятность, что и другим понадобится.

Универсального решения для почтового сервера нет. Но я постараюсь написать типа «сделайте вот так и потом, когда всё будет работать как надо — выкиньте лишнее».

Имеется сервер Colocation у компании tech.ru. Есть возможность сравнить с OVH, Hetzner, AWS. Для решения данной задачи гораздо эффективнее будет сотрудничество именно с tech.ru.

На сервере установлен Debian 9.

На сервере 2 интерфейса `eno1` и `eno2`. Первый безлимитный, а второй быстрый соответственно.

Имеется 3 статических IP адреса, XX.XX.XX.X0 и XX.XX.XX.X1 и XX.XX.XX.X2 на интерфейсе `eno1` и XX.XX.XX.X5 на интерфейсе `eno2`.

Имеется XXXX:XXXX:XXXX:XXXX::/64 пул IPv6 адресов, которые назначены на интерфейс `eno1` и из него XXXX:XXXX:XXXX:XXXX:1:2::/96 по моей просьбе назначили на `eno2`.

Имеется 3 домена `domain1.com`, `domain2.com`, `domain3.com`. Для `domain1.com` и `domain3.com` есть SSL сертификат.

Имеется google аккаунт, на который хочется привязать почтовый ящик `vasya.pupkin@domain1.com` (получение почты и отправка почты прямо из gmail интерфейса).
Должен быть почтовый ящик `support@domain2.com`, копию почты с которого я хочу видеть у себя в gmail. И в редко иметь возможность отправить чего-то от имени `support@domain2.com` через web-интерфейс.

Должен быть почтовый ящик `ivanov@domain3.com`, которым будет пользоваться Иванов со своего iPhone.

Отправляемые письма должны соответствовать всем современным требованиям к антиспаму.
Должен быть наивысший уровень шифрования предусмотренный в публичных сетях.
Должна быть поддержка IPv6 и для отправки и для получения писем.
Должен быть SpamAssassin, который никогда не будет удалять письма. А будет или bounce делать или пропускать или отправлять в IMAP папку «Спам».
Должно быть настроено авто-обучение SpamAssassin: если я перемещаю письмо в папку «Спам» — обучится на этом; если я перемещаю письмо из папки «Спам» — обучится на этом. Результаты обучения SpamAssassin — должны влиять на попадаемость письма в папку «Спам».
Скрипты php должны уметь отправлять почту от имени любого домена на данном сервере.
Должен быть openvpn сервис, с возможностью использовать IPv6 на клиенте, у которого нет IPv6.

Сначала надо настроить интерфейсы и маршрутизацию, включая IPv6.
Потом надо будет настроить OpenVPN, который будет соединяться по IPv4 и предоставлять клиенту статический-реальный IPv6 адрес. У этого клиента будет доступ ко всем IPv6 сервисам на сервере и доступ к любым ресурсам IPv6 в интернете.
Потом надо будет настроить Postfix на отправку писем + SPF + DKIM + rDNS и прочие тому подобные мелочи.
Потом надо будет настроить Dovecot и настроить Multidomain.
Потом надо будет настроить SpamAssassin и настроить обучение.
В завершение установить Bind.

============= Multi-interfaces =============


Для настройки интерфейсов надо прописать вот такое в "/etc/network/interfaces".

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
allow-hotplug eno1
iface eno1 inet static
        address XX.XX.XX.X0/24
        gateway XX.XX.XX.1
        dns-nameservers 127.0.0.1 213.248.1.6
        post-up ip route add XX.XX.XX.0/24 dev eno1 src XX.XX.XX.X0 table eno1t
        post-up ip route add default via XX.XX.XX.1 table eno1t
        post-up ip rule add table eno1t from XX.XX.XX.X0
        post-up ip rule add table eno1t to XX.XX.XX.X0

auto eno1:1
iface eno1:1 inet static
address XX.XX.XX.X1
netmask 255.255.255.0
        post-up ip rule add table eno1t from XX.XX.XX.X1
        post-up ip rule add table eno1t to XX.XX.XX.X1
        post-up   ip route add 10.8.0.0/24 dev tun0 src XX.XX.XX.X1 table eno1t
        post-down ip route del 10.8.0.0/24 dev tun0 src XX.XX.XX.X1 table eno1t

auto eno1:2
iface eno1:2 inet static
address XX.XX.XX.X2
netmask 255.255.255.0
        post-up ip rule add table eno1t from XX.XX.XX.X2
        post-up ip rule add table eno1t to XX.XX.XX.X2

iface eno1 inet6 static
        address XXXX:XXXX:XXXX:XXXX:1:1::/64
        gateway XXXX:XXXX:XXXX:XXXX::1
        up   ip -6 addr add XXXX:XXXX:XXXX:XXXX:1:1:1:1/64 dev $IFACE
        up   ip -6 addr add XXXX:XXXX:XXXX:XXXX:1:1:1:2/64 dev $IFACE
        down ip -6 addr del XXXX:XXXX:XXXX:XXXX:1:1:1:1/64 dev $IFACE
        down ip -6 addr del XXXX:XXXX:XXXX:XXXX:1:1:1:2/64 dev $IFACE

# The secondary network interface
allow-hotplug eno2
iface eno2 inet static
        address XX.XX.XX.X5
        netmask 255.255.255.0
        post-up   ip route add XX.XX.XX.0/24 dev eno2 src XX.XX.XX.X5 table eno2t
        post-up   ip route add default via XX.XX.XX.1 table eno2t
        post-up   ip rule add table eno2t from XX.XX.XX.X5
        post-up   ip rule add table eno2t to XX.XX.XX.X5
        post-up   ip route add 10.8.0.0/24 dev tun0 src XX.XX.XX.X5 table eno2t
        post-down ip route del 10.8.0.0/24 dev tun0 src XX.XX.XX.X5 table eno2t

iface eno2 inet6 static
        address XXXX:XXXX:XXXX:XXXX:1:2::/96
        up   ip -6 addr add XXXX:XXXX:XXXX:XXXX:1:2:1:1/64 dev $IFACE
        up   ip -6 addr add XXXX:XXXX:XXXX:XXXX:1:2:1:2/64 dev $IFACE
        down ip -6 addr del XXXX:XXXX:XXXX:XXXX:1:2:1:1/64 dev $IFACE
        down ip -6 addr del XXXX:XXXX:XXXX:XXXX:1:2:1:2/64 dev $IFACE

# OpenVPN network
iface tun0 inet6 static
        address XXXX:XXXX:XXXX:XXXX:1:3::/80

Данные настройки можно применять на любом сервере в tech.ru (с небольшим согласованием с поддержкой) и оно сразу заработает как надо.

Если опыт настройки аналогичных вещей для Hetzner, OVH — там подругому. Сложнее.

eno1 — это название сетевой карты #1 (медленный, но безлимитный).
eno2 — это название сетевой карты #2 (быстрый, но с тарифом).
tun0 — это название виртуальной сетевой карты от OpenVPN.
XX.XX.XX.X0 — IPv4 #1 на eno1.
XX.XX.XX.X1 — IPv4 #2 на eno1.
XX.XX.XX.X2 — IPv4 #3 на eno1.
XX.XX.XX.X5 — IPv4 #1 на eno2.
XX.XX.XX.1 — IPv4 gateway.
XXXX:XXXX:XXXX:XXXX::/64 — IPv6 на весь сервер.
XXXX:XXXX:XXXX:XXXX:1:2::/96 — IPv6 для eno2, всё остальное извне заходит в eno1.
XXXX:XXXX:XXXX:XXXX::1 — IPv6 gateway (стоит отметить, что тут можно/нужно сделать подругому. Указать IPv6 свича).
dns-nameservers — указаны 127.0.0.1 (потому, что установлен bind локально) и 213.248.1.6 (это от tech.ru ).

«table eno1t» и «table eno2t» — cмысл этих route-rule в том, чтобы трафик вошедший через eno1 -> ушёл бы через него же, а трафик вошедший через eno2 -> ушёл бы через него же. А также соединения по инициативе сервера уходили бы через eno1.

ip route add default via XX.XX.XX.1 table eno1t

Этой командой мы задаём, что любой непонятный трафик, который попал под любое rule у которого отмечено «table eno1t» -> направить в интерфейс eno1.

ip route add XX.XX.XX.0/24 dev eno1 src XX.XX.XX.X0 table eno1t

Этой командой мы задаём, что любой трафик по инициативе сервера направить в интерфейс eno1.

ip rule add table eno1t from XX.XX.XX.X0
ip rule add table eno1t to XX.XX.XX.X0

Этой командой мы задаём сами правила маркировки трафика.

auto eno1:2
iface eno1:2 inet static
address XX.XX.XX.X2
netmask 255.255.255.0
        post-up ip rule add table eno1t from XX.XX.XX.X2
        post-up ip rule add table eno1t to XX.XX.XX.X2

Этот блок задаёт второй IPv4 для интерфейса eno1.

ip route add 10.8.0.0/24 dev tun0 src XX.XX.XX.X1 table eno1t

Этой командой мы задаём route от клиентов OpenVPN до локальных IPv4 кроме XX.XX.XX.X0.
Почему этой команды достаточно для всех IPv4 — я до сих пор не понимаю.

iface eno1 inet6 static
        address XXXX:XXXX:XXXX:XXXX:1:1::/64
        gateway XXXX:XXXX:XXXX:XXXX::1

Это мы задаём адрес для самого интерфейса. Сервер его будет использовать как «исходящий» адрес. Больше никак использоваться не будет.

Почему указано ":1:1::" так сложно? Чтобы OpenVPN работало правильно и только для этого. Об этом подробнее позже.

На тему gateway — так работает и ладно. Но по правильному — сюда надо указать IPv6 свича к которому подсоединён сервер.

Однако почему-то IPv6 перестаёт работать, если я так делаю. Наверное это заморочки tech.ru какие-то.

ip -6 addr add XXXX:XXXX:XXXX:XXXX:1:1:1:1/64 dev $IFACE

Это добавление IPv6 адреса на интерфейс. Если надо сотню адресов — значит сотню строк в этом файле.

iface eno1 inet6 static
        address XXXX:XXXX:XXXX:XXXX:1:1::/64
...
iface eno2 inet6 static
        address XXXX:XXXX:XXXX:XXXX:1:2::/96
...
iface tun0 inet6 static
        address XXXX:XXXX:XXXX:XXXX:1:3::/80

Отметил адреса и подсети всех интерфейсов, чтобы было наглядно.
eno1 — обязательно должно быть "/64" — потому что это весь наш pool адресов.
tun0 — подсеть должны быть обязательно больше eno1. Иначе нельзя будет настроить IPv6 gateway для клиентов OpenVPN.
eno2 — подсеть должны быть обязательно больше tun0. Иначе клиенты OpenVPN не смогут попасть на IPv6 адреса локальные.
Для наглядности я выбрал шаг подсети 16, но при желании можно даже «1» шаг делать.
Соответственно 64+16 = 80, а 80+16 = 96.

Для ещё большей наглядности:
XXXX:XXXX:XXXX:XXXX:1:1:YYYY:YYYY — это адреса, которые должны быть назначены конкретным сайтам или сервисам на интерфейсе eno1.
XXXX:XXXX:XXXX:XXXX:1:2:YYYY:YYYY — это адреса, которые должны быть назначены конкретным сайтам или сервисам на интерфейсе eno2.
XXXX:XXXX:XXXX:XXXX:1:3:YYYY:YYYY — это адреса, которые должны быть назначены клиентам OpenVPN или использоваться как служебные адреса OpenVPN.


Для настройки сети — должна быть возможность перезагружать сервер.
IPv4 изменения подхватываются при выполнении (обязательно завернуть в screen — иначе эта команда просто уронит сеть на сервере):

/etc/init.d/networking restart

В файл "/etc/iproute2/rt_tables" добавить в конец:

100 eno1t
101 eno2t

Без этого нельзя использовать кастомные table в файле "/etc/network/interfaces".
Цифры должны быть уникальные и менее 65535.

IPv6 изменения изменяются легко без перезагрузки, но для этого нужно научиться как минимум трём командам:

ip -6 addr ...
ip -6 route ...
ip -6 neigh ...

Настройка "/etc/sysctl.conf"

# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward = 1

# Do not accept ICMP redirects (prevent MITM attacks)
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0

# Do not send ICMP redirects (we are not a router)
net.ipv4.conf.all.send_redirects = 0

# For receiving ARP replies
net.ipv4.conf.all.arp_filter = 0
net.ipv4.conf.default.arp_filter = 0

# For sending ARP
net.ipv4.conf.all.arp_announce = 0
net.ipv4.conf.default.arp_announce = 0

# Enable IPv6
net.ipv6.conf.all.disable_ipv6 = 0
net.ipv6.conf.default.disable_ipv6 = 0
net.ipv6.conf.lo.disable_ipv6 = 0

# IPv6 configuration
net.ipv6.conf.all.autoconf = 1
net.ipv6.conf.all.accept_ra = 0

# For OpenVPN
net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.all.proxy_ndp = 1

# For nginx on boot
net.ipv6.ip_nonlocal_bind = 1

Это настройки «sysctl» моего сервера. Отмечу важное.

net.ipv4.ip_forward = 1

Без этого OpenVPN не будет работать никак.

net.ipv6.ip_nonlocal_bind = 1

Любой, кто попытается сделать bind IPv6 (например nginx) сразу после того, как интерфейс подялся — получит ошибку. Что такой адрес недоступен.

Чтобы избежать такой ситуации и делается такая настройка.

net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.all.proxy_ndp = 1

Без этих настроек IPv6 трафик от клиента OpenVPN не выходит в мир.

Другие настройки или не относятся к делу или я не помню зачем они.
Но на всякий случай оставляю «как есть».

Для того, чтобы изменения этого файла подхватились без перезагрузки сервера — надо выполнить команду:

sysctl -p

Более детально про «table» правила: habr.com/post/108690

============= OpenVPN =============


OpenVPN IPv4 не работает без iptables.

У меня iptables вот такие для VPN:

iptables -A INPUT -p udp -s YY.YY.YY.YY --dport 1194 -j ACCEPT
iptables -A FORWARD -i tun0 -o eno1 -j ACCEPT
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eno1 -j SNAT --to-source XX.XX.XX.X0
##iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eno1 -j MASQUERADE
iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -p udp --dport 1194 -j DROP
iptables -A FORWARD -p udp --dport 1194 -j DROP

YY.YY.YY.YY — это мой статический IPv4 адрес локальной машины.
10.8.0.0/24 — IPv4 сеть openvpn. IPv4 адреса для клиентов openvpn.
Последовательность правил важна.

iptables -A INPUT -p udp -s YY.YY.YY.YY --dport 1194 -j ACCEPT
iptables -A FORWARD -i tun0 -o eno1 -j ACCEPT
...
iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -p udp --dport 1194 -j DROP
iptables -A FORWARD -p udp --dport 1194 -j DROP

Это ограничение, чтобы только я со своего статического IP мог бы воспользоваться OpenVPN.

iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eno1 -j SNAT --to-source XX.XX.XX.X0
  -- или --
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eno1 -j MASQUERADE

Для пробрасывания IPv4 пакетов между клиентами OpenVPN и интернетом — нужно прописать одну из этих команд.

Для разных случаев один из вариантов не подходит.
Для моего случая подходят обе команды.
Почитав документацию я выбрал первый вариант, потому что он кушает меньше CPU.

Чтобы все настройки iptables подхватывались после reboot — надо сохранить их куда-то.

iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6

Такие имена выбраны не случайно. Их использует пакет «iptables-persistent».

apt-get install iptables-persistent

Установка основного пакета OpenVPN:

apt-get install openvpn easy-rsa

Настроим шаблон для сертификатов (подставить свои значения):

make-cadir ~/openvpn-ca
cd ~/openvpn-ca
ln -s openssl-1.0.0.cnf openssl.cnf

Отредактируем настройки шаблона сертификатов:

mcedit vars

...
# These are the default values for fields
# which will be placed in the certificate.
# Don't leave any of these fields blank.
export KEY_COUNTRY="RU"
export KEY_PROVINCE="Krasnodar"
export KEY_CITY="Dinskaya"
export KEY_ORG="Own"
export KEY_EMAIL="admin@domain1.com"
export KEY_OU="VPN"

# X509 Subject Field
export KEY_NAME="server"
...

Создаём серверный сертификат:

cd ~/openvpn-ca
source vars
./clean-all
./build-ca
./build-key-server server
./build-dh
openvpn --genkey --secret keys/ta.key

Приготовим возможность создавать итоговые «client-name.opvn» файлы:

mkdir -p ~/client-configs/files
chmod 700 ~/client-configs/files
cp /usr/share/doc/openvpn/examples/sample-config-files/client.conf ~/client-configs/base.conf
mcedit ~/client-configs/base.conf

# Client mode
client

# Interface tunnel type
dev tun

# TCP protocol
proto tcp-client

# Address/Port of VPN server
remote XX.XX.XX.X0 1194

# Don't bind to local port/address
nobind

# Don't need to re-read keys and re-create tun at restart
persist-key
persist-tun

# Remote peer must have a signed certificate
remote-cert-tls server
ns-cert-type server

# Enable compression
comp-lzo

# Custom
ns-cert-type server
tls-auth ta.key 1
cipher DES-EDE3-CBC

Приготовим скрипт, который будет сшивать все файлы в единый opvn файл.

mcedit ~/client-configs/make_config.sh
chmod 700 ~/client-configs/make_config.sh

#!/bin/bash

# First argument: Client identifier

KEY_DIR=~/openvpn-ca/keys
OUTPUT_DIR=~/client-configs/files
BASE_CONFIG=~/client-configs/base.conf

cat ${BASE_CONFIG}     <(echo -e '<ca>')     ${KEY_DIR}/ca.crt     <(echo -e '</ca>\n<cert>')     ${KEY_DIR}/${1}.crt     <(echo -e '</cert>\n<key>')     ${KEY_DIR}/${1}.key     <(echo -e '</key>\n<tls-auth>')     ${KEY_DIR}/ta.key     <(echo -e '</tls-auth>')     > ${OUTPUT_DIR}/${1}.ovpn

Создаём первого клиента OpenVPN:

cd ~/openvpn-ca
source vars
./build-key client-name
cd ~/client-configs
./make_config.sh client-name

Файл "~/client-configs/files/client-name.ovpn" отправляем на утройство клиенту.

Для iOS клиентов надо будет сделать трюк:
Содержимое тэга «tls-auth» должно быть без комментариев.
А также поставить «key-direction 1» сразу перед тэгом «tls-auth».

Настроим конфиг OpenVPN сервера:

cd ~/openvpn-ca/keys
cp ca.crt ca.key server.crt server.key ta.key dh2048.pem /etc/openvpn
gunzip -c /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz | tee /etc/openvpn/server.conf
mcedit /etc/openvpn/server.conf

# Listen port
port 1194

# Protocol
proto tcp-server

# IP tunnel
dev tun0
tun-ipv6
push tun-ipv6

# Master certificate
ca ca.crt

# Server certificate
cert server.crt

# Server private key
key server.key

# Diffie-Hellman parameters
dh dh2048.pem

# Allow clients to communicate with each other
client-to-client

# Client config dir
client-config-dir /etc/openvpn/ccd

# Run client-specific script on connection and disconnection
script-security 2
client-connect "/usr/bin/sudo -u root /etc/openvpn/server-clientconnect.sh"
client-disconnect "/usr/bin/sudo -u root /etc/openvpn/server-clientdisconnect.sh"

# Server mode and client subnets
server 10.8.0.0 255.255.255.0
server-ipv6 XXXX:XXXX:XXXX:XXXX:1:3::/80
topology subnet

# IPv6 routes
push "route-ipv6 XXXX:XXXX:XXXX:XXXX::/64"
push "route-ipv6 2000::/3"

# DNS (for Windows)
# These are OpenDNS
push "dhcp-option DNS 208.67.222.222"
push "dhcp-option DNS 208.67.220.220"

# Configure all clients to redirect their default network gateway through the VPN
push "redirect-gateway def1 bypass-dhcp"
push "redirect-gateway ipv6" #For iOS

# Don't need to re-read keys and re-create tun at restart
persist-key
persist-tun

# Ping every 10s. Timeout of 120s.
keepalive 10 120

# Enable compression
comp-lzo

# User and group
user vpn
group vpn

# Log a short status
status openvpn-status.log

# Logging verbosity
##verb 4

# Custom config
tls-auth ta.key 0
cipher DES-EDE3-CBC

Это нужно для того, чтобы задать статический адрес каждому клиенту (не обязательно, но я использую):

# Client config dir
client-config-dir /etc/openvpn/ccd

Самая сложная и ключевая деталь.

К сожалению OpenVPN ещё не умеет самостоятельно настраивать IPv6 gateway для клиентов.
Приходится «вручную» пробрасывать это для каждого клиента.

# Run client-specific script on connection and disconnection
script-security 2
client-connect "/usr/bin/sudo -u root /etc/openvpn/server-clientconnect.sh"
client-disconnect "/usr/bin/sudo -u root /etc/openvpn/server-clientdisconnect.sh"

Файл "/etc/openvpn/server-clientconnect.sh":

#!/bin/sh

# Check client variables
if [ -z "$ifconfig_pool_remote_ip" ] || [ -z "$common_name" ]; then
        echo "Missing environment variable."
        exit 1
fi

# Load server variables
. /etc/openvpn/variables

ipv6=""

# Find out if there is a specific config with fixed IPv6 for this client
if [ -f "/etc/openvpn/ccd/$common_name" ]; then
        # Get fixed IPv6 from client config file
        ipv6=$(sed -nr 's/^.*ifconfig-ipv6-push[ \t]+([0-9a-fA-F\\:]+).*$/\1/p' "/etc/openvpn/ccd/$common_name")
        echo $ipv6
fi

# Get IPv6 from IPv4
if [ -z "$ipv6" ]; then
        ipp=$(echo "$ifconfig_pool_remote_ip" | cut -d. -f4)
        if ! [ "$ipp" -ge 2 -a "$ipp" -le 254 ] 2>/dev/null; then
                echo "Invalid IPv4 part."
                exit 1
        fi
        hexipp=$(printf '%x' $ipp)
        ipv6="$prefix$hexipp"
fi

# Create proxy rule
/sbin/ip -6 neigh add proxy $ipv6 dev eno1


Файл "/etc/openvpn/server-clientdisconnect.sh":
#!/bin/sh

# Check client variables
if [ -z "$ifconfig_pool_remote_ip" ] || [ -z "$common_name" ]; then
        echo "Missing environment variable."
        exit 1
fi

# Load server variables
. /etc/openvpn/variables

ipv6=""

# Find out if there is a specific config with fixed IPv6 for this client
if [ -f "/etc/openvpn/ccd/$common_name" ]; then
        # Get fixed IPv6 from client config file
        ipv6=$(sed -nr 's/^.*ifconfig-ipv6-push[ \t]+([0-9a-fA-F\\:]+).*$/\1/p' "/etc/openvpn/ccd/$common_name")
fi

# Get IPv6 from IPv4
if [ -z "$ipv6" ]; then
        ipp=$(echo "$ifconfig_pool_remote_ip" | cut -d. -f4)
        if ! [ "$ipp" -ge 2 -a "$ipp" -le 254 ] 2>/dev/null; then
                echo "Invalid IPv4 part."
                exit 1
        fi
        hexipp=$(printf '%x' $ipp)
        ipv6="$prefix$hexipp"
fi

# Delete proxy rule
/sbin/ip -6 neigh del proxy $ipv6 dev eno1

Оба скрипта используют файл "/etc/openvpn/variables":

# Subnet
prefix=XXXX:XXXX:XXXX:XXXX:2:
# netmask
prefixlen=112

Почему тут так написано — затрудняюсь вспомнить.

Сейчас выглядит странным netmask = 112 (тут же 96 должен быть).
И prefix странный, не соответствует сети tun0.
Но ладно, оставляю «как есть».

cipher DES-EDE3-CBC

Это на любителя — я выбрал такой способ шифрования соединения.

Более детально про настройку OpenVPN IPv4.

Более детально про настройку OpenVPN IPv6.

============= Postfix =============


Установка основного пакета:

apt-get install postfix

При установке выбрать «internet-site».

Мой "/etc/postfix/main.cf" выглядит так:

smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

readme_directory = no

# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on
# fresh installs.
compatibility_level = 2

# TLS parameters
smtpd_tls_cert_file=/etc/ssl/domain1.com.2018.chained.crt
smtpd_tls_key_file=/etc/ssl/domain1.com.2018.key
smtpd_use_tls=yes
smtpd_tls_auth_only = yes
smtp_bind_address = XX.XX.XX.X0
smtp_bind_address6 = XXXX:XXXX:XXXX:XXXX:1:1:1:1

smtp_tls_security_level = may
smtp_tls_ciphers = export
smtp_tls_protocols = !SSLv2, !SSLv3
smtp_tls_loglevel = 1

smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = domain1.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = domain1.com
mydestination = localhost
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = ipv4

internal_mail_filter_classes = bounce

# Storage type
virtual_transport = lmtp:unix:private/dovecot-lmtp
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf

# SMTP-Auth settings
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_recipient_restrictions =
        permit_sasl_authenticated,
        permit_mynetworks,
        #reject_invalid_hostname,
        #reject_unknown_recipient_domain,
        reject_unauth_destination,
        reject_rbl_client sbl.spamhaus.org,
        check_policy_service unix:private/policyd-spf

smtpd_helo_restrictions =
        #reject_invalid_helo_hostname,
        #reject_non_fqdn_helo_hostname,
        reject_unknown_helo_hostname

smtpd_client_restrictions =
        permit_mynetworks,
        permit_sasl_authenticated,
        reject_non_fqdn_helo_hostname,
        permit

# SPF
policyd-spf_time_limit = 3600

# OpenDKIM
milter_default_action = accept
milter_protocol = 6
smtpd_milters = unix:var/run/opendkim/opendkim.sock
non_smtpd_milters = unix:var/run/opendkim/opendkim.sock

# IP address per domain
sender_dependent_default_transport_maps = pcre:/etc/postfix/sdd_transport.pcre

Рассмотрим детали этого конфига.

smtpd_tls_cert_file=/etc/ssl/domain1.com.2018.chained.crt
smtpd_tls_key_file=/etc/ssl/domain1.com.2018.key



По мнению хабровчан данный блок содержит `дезинформацию и неверные тезисы`.
Только спустя 8 лет после начала моей карьеры я стал понимать как работает SSL.

Поэтому я возьму на себя смелость описать как пользоваться SSL (не отвечая на вопросы «Как это работает?» и «Почему это работает?»).

Основа современного шифрования — это создание пары ключей (две очень длинные строки символов).

Один «ключ» приватный, другой ключ «публичный». Приватный ключ храним очень старательно в секрете. Публичный ключ раздаём всем желающим.

При помощи публичного ключа можно зашифровать строку текста так, что расшифровать сможет только владелец приватного ключа.
Ну вот и вся основа технологии.

Шаг №1 — https сайты.
Браузер при обращении к сайту узнаёт от веб сервера, что сайт https и поэтому запрашивает публичный ключ.
Веб сервер отдаёт публичный ключ. Браузер используя публичный ключ зашифровывает http-request и отправляет его.
Контент http-request может прочитать только тот у кого есть приватный ключ, то есть только сервер к которому выполняется обращение.
Http-request содержит в себе как минимум URI. Поэтому если в стране пытыются ограничить доступ не ко всему сайту, а к конкретной странице — то для https сайтов это сделать невозможно.

Шаг №2 — зашифрованный ответ.
Веб сервер даёт ответ, который легко могут прочитать по дороге.
Решение предельно простое — браузер у себя локально формирует такую-же пару приватный-публичный ключ для каждого https сайта.
И вместе с запросом публичного ключа сайта отправляет свой локальный публичный ключ.
Веб сервер запоминает его и при отправке http-response шифрует этим вот публичным ключём конкретного клиента.
Теперь http-response может расшифровать только обладатель приватного ключа браузера клиента (то есть сам клиент).

Шаг №3 — установка защищённого соединения по публичному каналу.
В примере №2 есть уязвимость — ничего не мешает доброжелателям перехватить http-request и подредактировать информацию о публичном ключе.
Таким образом посредник будет пракрасно видеть весь контент отправляемых-получаемых сообщений пока не сменится канал связи.
Бороться с этим предельно просто — достаточно отправить публичный ключ браузера как сообщение зашифрованное публичным ключём веб сервера.
Веб сервер тогда первым делом отправляет ответ типа «твой публичный ключ вот такой вот» и шифрует это сообщение этим же публичным ключём.
Браузер смотрит ответ — если пришло сообщение «твой публичный ключ вот такой вот» — то это 100% гарантия, что данный канал связи безопасен.
Насколько безопасен?
Само создание такого безопасного канала связи происходит со скоростью ping*2. Например 20мс.
Злоумышленник должен или заранее иметь приватный ключ одной из сторон. Или подобрать приватный ключ за пару милисекунд.
Взлом одного современного приватнога ключа займт десятилетия на суперкомпьютере.

Шаг №4 — публичная БД публичных ключей.
Очевидно, что во всей этой истории существует возможность для злоумышленика сидящего на канале связи между клиентом и сервером.
Возможность клиенту предствится сервером, а серверу представться клиентом. И сэмулировать пару ключей в обе стороны.
Тогда злоумышленик будет видеть весь трафик и будет иметь возможность «подредактировать» трафик.
Например изменить адрес куда отправлять деньги или скопировать пароль от онлайн-банка или заблокировать «неугодный» контент.
Для борьбы с такими злоумышленниками придумали публичную БД с публичными ключами для каждого https сайта.
Каждый браузер «знает» о существовании около 200 таких БД. Это предустановлено в каждый браузер.
«Знание» подкреплено публичным ключём от каждого сертификата. То есть соединение с каждым конкретным центром сертификации подделать невозможно.

Теперь есть простое понимание как пользоваться SSL для https.
Если пошевелить мозгами — то станет понятно, как спец-службы могут в этой конструкции чего-то взломать. Но это им будет стоить чудовищных усилий.
А организациям меньше АНБ или ЦРУ — практически невозможно взломать существующий уровень защиты даже для vip.

Ещё добавлю про ssh соединения. Там никаких публичных ключей нет, как же быть. Вопрос решается двумя способами.
Вариант ssh-по-паролю:
При первом соединении ssh-клиент должен предупредить, что тут у нас новый публичный ключ от ssh-сервера.
И при дальнейших соединениях если появилось предупреждение «новый публичный ключ от ssh-сервера» — будет означать, что вас пытаются прослушать.
Или при первом соединении вас прослушивали, а теперь вы общаетесь с сервером без посредников.
Собственно из-за того, что факт прослушки легко, быстро и без усилий вскрывается — этой атакой пользуются только в особых случаях под конкретного клиента.

Вариант ssh-по-ключу:
Берём флэшку, записываем на неё приватный ключ для ssh-сервера (для этого есть термины и куча нюансов существенных, но я пишу ликбез, а не инструкцию по применению).
Публичный ключ оставляем на машине где будет ssh-клиент и его тоже держим в секрете.
Приносим флэшку к серверу, вставляем, копируем приватный ключ, а флэшку сжигаем и развеиваем прах по ветру (или хотябы форматируем с заполнением нулями).
Вот и всё — после такой операции будет невозможно взломать такое ssh соединение. Разумеется лет за 10 на суперкомпьютере можно будет посмотреть трафик — но это отдельная история.

Прошу прощения за оффтоп.
Итак, теперь когда известна теория. Расскажу про flow создания ssl сертификата.

При помощи «openssl genrsa» мы создаём приватный ключ и «заготовок» для публичного ключа.
«заготовок» отправляем сторонней компании, которой мы платим примерно $9 за самый простой сертификат.

Через пару часов мы получаем от этой сторонней компании наш «публичный» ключ и ещё набор нескольких публичных ключей.

Зачем сторонней компании платить за оформление моего публичного ключа — вопрос отдельный, тут рассматривать не будем.

Теперь понятно в чём смысл надписи:

smtpd_tls_key_file=/etc/ssl/domain1.com.2018.key

В папке "/etc/ssl" сложены все файлы для ssl вопросов.
domain1.com — название домена.
2018 — год создания ключей.
«key» — обозначение, что файл приватный-ключ.

И смысл этого файла:

smtpd_tls_cert_file=/etc/ssl/domain1.com.2018.chained.crt
domain1.com — название домена.
2018 — год создания ключей.
chained — обозначение, что тут цепочка публичных ключей (первый — наш публичный и остальные — что пришло от компании оформившей публичный ключ).
crt — обозначение, что тут готовый сертификат (публичный ключ с пояснениями техническими).

smtp_bind_address = XX.XX.XX.X0
smtp_bind_address6 = XXXX:XXXX:XXXX:XXXX:1:1:1:1

Это установка в данном случае не используется, но написано для примера.

Потому что ошибка в данном параметре приведёт к отправке от вашего сервера спама (без вашей воли).

Потом доказывайте всем, что вы не виноваты.

recipient_delimiter = +

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

Например если у вас есть почтовый ящик «username@gmail.com» попробуйте отправть на «username+spam@gmail.com» — посмотрите что из этого получится.

inet_protocols = ipv4

Возможно это будет сбивать с толку.

Но это не просто так. Каждый новый домен — по умолчанию только IPv4, потом включаю IPv6 для каждого в отдельности.

virtual_transport = lmtp:unix:private/dovecot-lmtp
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf

Тут мы задаём, что вся входящая почта уходит в dovecot.
А правила для domain, mailbox, alias — смотреть в БД.

/etc/postfix/mysql-virtual-mailbox-domains.cf

user = usermail
password = mailpassword
hosts = 127.0.0.1
dbname = servermail
query = SELECT 1 FROM virtual_domains WHERE name='%s'

/etc/postfix/mysql-virtual-mailbox-maps.cf

user = usermail
password = mailpassword
hosts = 127.0.0.1
dbname = servermail
query = SELECT 1 FROM virtual_users WHERE email='%s'

/etc/postfix/mysql-virtual-alias-maps.cf

user = usermail
password = mailpassword
hosts = 127.0.0.1
dbname = servermail
query = SELECT destination FROM virtual_aliases WHERE source='%s'

# SMTP-Auth settings
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes

Теперь postfix знает, что принимать почту для дальнейшей отправки можно только по авторизации с dovecot.

Мне правда не очень понятно, зачем это тут дублировать. Мы же уже указали в «virtual_transport» всё что надо.

Но postfix система очень старая — наверное это кастыли от старых времён.

smtpd_recipient_restrictions =
        ...

smtpd_helo_restrictions =
        ...

smtpd_client_restrictions =
        ...

Это настраивать для каждого почтового сервера по своему.

В моём распоряжении есть 3 почтовых сервера и эти настройки очень разные из-за разных требований к использованию.

Настраивать надо внимательно — иначе спам хлынет к вам или ещё хуже — спам хлынет от вас.

# SPF
policyd-spf_time_limit = 3600

Настройка для какого-то плагина связанного с проверкой SPF входящих писем.

# OpenDKIM
milter_default_action = accept
milter_protocol = 6
smtpd_milters = unix:var/run/opendkim/opendkim.sock
non_smtpd_milters = unix:var/run/opendkim/opendkim.sock

Настройка, что все исходящие письма мы должны снабжать DKIM подписью.

# IP address per domain
sender_dependent_default_transport_maps = pcre:/etc/postfix/sdd_transport.pcre

Это ключевая деталь в маршрутизации писем при оотправки писем от php скриптов.

Файл "/etc/postfix/sdd_transport.pcre":

/^www-domain1@domain1\.com$/ domain1:
/^www-domain2@domain1\.com$/ domain2:
/^www-domain3@domain1\.com$/ domain3:
/@domain1\.com$/             domain1:
/@domain2\.com$/             domain2:
/@domain3\.com$/             domain3:

Слева — регулярные выражения. Справа — метка, которой отмечается письмо.
Postfix в соответствии с меткой — учтёт ещё несколько строк конфигурации для конкретного письма.

Как именно будет переконфигурирован postfix для конкретного письма — будет указано в «master.cf».

Строки 4, 5, 6 — они главные. От имени какого домена отправляем письмо — такую метку и ставим.
Но не всегда в php скриптах в старом коде указывается поле «from». Тогда на помощь приходит имя пользователя.

Статья и так обширная — не хотелось бы отвлекаться на настройку nginx+fpm.

Кратко — мы для каждого сайта задаём своего linux-user владельца. И соответственно свой fpm-pool.

Fpm-pool использует любую версию php (это прекрасно когда на одном сервере без проблем для соседних сайтов можно использовать разную версию php и даже разный php.ini).

Так вот у конкретного linux-user «www-domain2» есть сайт domain2.com. На этом сайте есть код отправки писем без указания поля from.

Так вот даже в таком случае письма будут уходить корректно и никогда не попадут в спам.
Мой "/etc/postfix/master.cf" выглядит так:

...
smtp      inet  n       -       y       -       -       smtpd
  -o content_filter=spamassassin
...
submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
...
policyd-spf  unix  -       n       n       -       0       spawn
    user=policyd-spf argv=/usr/bin/policyd-spf

spamassassin unix -     n       n       -       -       pipe
    user=spamd argv=/usr/bin/spamc -f -e
    /usr/sbin/sendmail -oi -f ${sender} ${recipient}
...
domain1  unix -       -       n       -       -       smtp
   -o smtp_bind_address=XX.XX.XX.X1
   -o smtp_helo_name=domain1.com
   -o inet_protocols=all
   -o smtp_bind_address6=XXXX:XXXX:XXXX:XXXX:1:1:1:1
   -o syslog_name=postfix-domain1

domain2  unix -       -       n       -       -       smtp
   -o smtp_bind_address=XX.XX.XX.X5
   -o smtp_helo_name=domain2.com
   -o inet_protocols=all
   -o smtp_bind_address6=XXXX:XXXX:XXXX:XXXX:1:2:1:1
   -o syslog_name=postfix-domain2

domain3  unix -       -       n       -       -       smtp
   -o smtp_bind_address=XX.XX.XX.X2
   -o smtp_helo_name=domain3
   -o inet_protocols=all
   -o smtp_bind_address6=XXXX:XXXX:XXXX:XXXX:1:1:5:1
   -o syslog_name=postfix-domain3

Файл приведён не полностью — он и так очень большой.
Отметил только то, что изменено.

smtp      inet  n       -       y       -       -       smtpd
  -o content_filter=spamassassin
...
spamassassin unix -     n       n       -       -       pipe
    user=spamd argv=/usr/bin/spamc -f -e
    /usr/sbin/sendmail -oi -f ${sender} ${recipient}

Это настройки связанные со spamassasin, о нём позже.

submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject

Разрешаем присоединяться к почтовому серверу через 587 порт.
Для этого обязательно авторизоваться.

policyd-spf  unix  -       n       n       -       0       spawn
    user=policyd-spf argv=/usr/bin/policyd-spf

Включаем проверку SPF.

apt-get install postfix-policyd-spf-python

Установим пакет для SPF проверок выше.

domain1  unix -       -       n       -       -       smtp
   -o smtp_bind_address=XX.XX.XX.X1
   -o smtp_helo_name=domain1.com
   -o inet_protocols=all
   -o smtp_bind_address6=XXXX:XXXX:XXXX:XXXX:1:1:1:1
   -o syslog_name=postfix-domain1

А это самое интересное. Это возможность отправлять письма для конкретного домена с конкретного IPv4/IPv6 адреса.

Делается это ради rDNS. rDNS — это получение какой-то строки по IP адресу.
И для почты эта возможность используется для подтверждения того, что helo точно соответствует rDNS того адреса, с которого отправили email.

Если helo не соответствует домену почты, от имени кого отправили письмо — начисляются спам очки.

Helo не соответствует rDNS — начисляется много спам очков.
Соответственно для каждого домена должен быть свой IP адрес.
Для OVH — в консольке есть возможность указывать rDNS.
Для tech.ru — через саппорт вопрос решается.
Для AWS — через саппорт вопрос решается.
«inet_protocols» и «smtp_bind_address6» — это мы включаем поддержку IPv6.
Для IPv6 тоже надо rDNS прописывать.
«syslog_name» — а это для удобства чтения логов.
Покупать сертификаты рекомендую тут.

Настройка связки postfix+dovecot тут.

Настройка SPF.

============= Dovecot =============


apt-get install dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-mysql dovecot-antispam

Настройка mysql, устанавливаем сами пакеты.

Файл "/etc/dovecot/conf.d/10-auth.conf"

disable_plaintext_auth = yes
auth_mechanisms = plain login

Авторизация только в зашифрованном виде.

Файл "/etc/dovecot/conf.d/10-mail.conf"

mail_location = maildir:/var/mail/vhosts/%d/%n

Тут укажем место хранения писем.

Я хочу, чтобы они хранились в файлах и были сгруппированы по доменам.

Файл "/etc/dovecot/conf.d/10-master.conf"

service imap-login {
  inet_listener imap {
    port = 0
  }
  inet_listener imaps {
    address = XX.XX.XX.X1, XX.XX.XX.X2, XX.XX.XX.X5, [XXXX:XXXX:XXXX:XXXX:1:1:1:1], [XXXX:XXXX:XXXX:XXXX:1:2:1:1], [XXXX:XXXX:XXXX:XXXX:1:1:5:1]
    port = 993
    ssl = yes
  }
}
service pop3-login {
  inet_listener pop3 {
    port = 0
  }
  inet_listener pop3s {
    address = XX.XX.XX.X1, XX.XX.XX.X2, XX.XX.XX.X5, [XXXX:XXXX:XXXX:XXXX:1:1:1:1], [XXXX:XXXX:XXXX:XXXX:1:2:1:1], [XXXX:XXXX:XXXX:XXXX:1:1:5:1]
    port = 995
    ssl = yes
  }
}
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0600
    user = postfix
    group = postfix
  }
}
service imap {
}
service pop3 {
}
service auth {
  unix_listener auth-userdb {
    mode = 0600
    user = vmail
  }

  unix_listener /var/spool/postfix/private/auth {
    mode = 0666
    user = postfix
    group = postfix
  }
  user = dovecot
}
service auth-worker {
  user = vmail
}
service dict {
  unix_listener dict {
  }
}

Это главный файл настроек dovecot.
Тут мы отключаем не защищённые соединения.
И включаем защищённые соединения.

Файл "/etc/dovecot/conf.d/10-ssl.conf"

ssl = required
ssl_cert = </etc/nginx/ssl/domain1.com.2018.chained.crt
ssl_key = </etc/nginx/ssl/domain1.com.2018.key
local XX.XX.XX.X5 {
  ssl_cert = </etc/nginx/ssl/domain2.com.2018.chained.crt
  ssl_key =  </etc/nginx/ssl/domain2.com.2018.key
}

Настраиваем ssl. Указываем, что ssl — обязательно.
И сам сертификат. И важная деталь — директива «local». Указывает, при соединении к какому локальному IPv4 — какой ssl сертификат использовать.

Кстати IPv6 тут не настроен, исправлю это упущение как-нить потом.
XX.XX.XX.X5 (domain2) — сертификата нет. Для соединения клиентов нужно указывать domain1.com.
XX.XX.XX.X2 (domain3) — сертификат есть, для соединения клиентов можно указывать domain1.com или domain3.com .
Файл "/etc/dovecot/conf.d/15-lda.conf"

protocol lda {
  mail_plugins = $mail_plugins sieve
}

Это в дальнейшем нужно будет для spamassassin.

Файл "/etc/dovecot/conf.d/20-imap.conf"

protocol imap {
  mail_plugins = $mail_plugins antispam
}

Это antispam плагин. Нужен для обучения spamassasin в момент переноса в/из папки «Spam».

Файл "/etc/dovecot/conf.d/20-pop3.conf"

protocol pop3 {
}

Просто такой файл есть.

Файл "/etc/dovecot/conf.d/20-lmtp.conf"

protocol lmtp {
  mail_plugins = $mail_plugins sieve
  postmaster_address = admin@domain1.com
}

Настройка lmtp.

Файл "/etc/dovecot/conf.d/90-antispam.conf"

plugin {
  antispam_backend = pipe
  antispam_trash = Trash;trash
  antispam_spam = Junk;Spam;SPAM
  antispam_pipe_program_spam_arg = --spam
  antispam_pipe_program_notspam_arg = --ham
  antispam_pipe_program = /usr/bin/sa-learn
  antispam_pipe_program_args = --username=%Lu
}

Настройки обучения spamassasin в момент переноса в/из папки «Spam».

Файл "/etc/dovecot/conf.d/90-sieve.conf"

plugin {
  sieve = ~/.dovecot.sieve
  sieve_dir = ~/sieve
  sieve_after = /var/lib/dovecot/sieve/default.sieve
}

Файл в котором указано что делать со входящими письмами.

Файл "/var/lib/dovecot/sieve/default.sieve"

require ["fileinto", "mailbox"];

if header :contains "X-Spam-Flag" "YES" {
        fileinto :create "Spam";
}

Надо скомпилировать файл: «sievec default.sieve».

Файл "/etc/dovecot/conf.d/auth-sql.conf.ext"

passdb {
  driver = sql
  args = /etc/dovecot/dovecot-sql.conf.ext
}
userdb {
  driver = static
  args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n
}

Указание sql файлов для авторизации.
А сам файл — как способ авторизации.

Файл "/etc/dovecot/dovecot-sql.conf.ext"

driver = mysql
connect = host=127.0.0.1 dbname=servermail user=usermail password=password
default_pass_scheme = SHA512-CRYPT
password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';

Это соответствует аналогичным настройкам для postfix.

Файл "/etc/dovecot/dovecot.conf"
protocols = imap lmtp pop3
listen = *, ::
dict {
}
!include conf.d/*.conf
!include_try local.conf

Основной файл конфигурации.
Важно то, что мы тут указываем-добавляем протоколы.

============= SpamAssassin =============


apt-get install spamassassin spamc

Установим пакеты.

adduser spamd --disabled-login

Добавим пользователя от имени которого.

systemctl enable spamassassin.service

Включаем авто-загрузку spamassassin сервис при загрузке.

Файл "/etc/default/spamassassin":

CRON=1

Включаев автоматическое обновление правил «по умолчанию».

Файл "/etc/spamassassin/local.cf":

report_safe 0

use_bayes          1
bayes_auto_learn   1
bayes_auto_expire  1
bayes_store_module Mail::SpamAssassin::BayesStore::MySQL
bayes_sql_dsn      DBI:mysql:sa:localhost:3306
bayes_sql_username sa
bayes_sql_password password

Нужно сделать в mysql БД «sa» с пользователем «sa» с паролем «password» (заменить на что-то адекватное).

report_safe — это вместо письма будет присылаться отчёт о письме-спаме.
use_bayes — это настройки машинного обучения spamassassin.

Остальные настройки spamassassin применялись ранее по статье.

Общая настройка «spamassassin».
Про перемещение новых Спам-писем в IMAP папку «Spam».
Про простую связку Dovecot + SpamAssassin.
Рекомендую к прочтению теория обучения spamassasin при движении писем в imap папках (и не рекомендую к применению).

============= Обращение к сообществу =============


Ещё хотелось бы закинуть идею в сообщество про то, как повысить уровень защищённости пересылаемых писем. Раз уж я так глубоко погрузился в тему почты.

Чтобы пользователь мог бы у себя на клиенте (outlook, thunderbird, browser-plugin, ...) создать пару ключей. Публичный и приватный. Публичный — отправить в DNS. Приватный — сохранять на клиенте. Почтовые сервера бы умели применять публичный ключ для отправки конкретному адресату.

И для защиты от спама при таких письмах (да, почтовый сервер жи не сможет посмотреть контент) — надо будет ввести 3 правила:

  1. Обязательная настоящая подпись DKIM, обязательный SPF, обязательный rDNS.
  2. Нейронная сеть на тему обучения антиспама + БД к ней на стороне клиента.
  3. Алгоритм шифрования должен быть таким, что отправляющая сторона должна потратить на шифрования в 100 раз больше мощностей CPU, чем принимающая сторона.

Кроме публичных писем — разработать стандарт письма-предложения «начать защищённую переписку». Один из пользователей (почтовый ящик) шлёт другому почтовому ящику письмо с аттачем. В письме текст-предложение начать защищённый канал связи для переписки и публичный ключ владельца почтового ящика (при этом приватный ключ на стороне клиента).

Можно даже пару ключей делать специально для каждой переписки. Пользователь-получатель может принять это предложение и отправить свой публичный ключ (тоже сделанный специально для данной переписки). Далее первый пользователь отправляет служебное контрольное письмо (зашифрованное публичным ключом второго пользователя) — при получении которого второй пользователь может считать сформированный канал связи надёжным. Далее второй пользователь отправляет контрольное письмо — и тогда первый пользователь тоже может считать сформированный канал защищённым.

Для борьбы с перехватом ключей по дороге — надо в протоколе предусмотреть возможность передачи хотя-бы одного публичного ключа при помощи флэшки.

И самое главное — чтобы это всё работало (вопрос «а кто за это заплатит?»):
Ввести почтовые сертификаты стоимостью от 10$ за 3 года. Которые будут позволять отправителю указать в dns, что «мои публичные ключи находятся вон-там». И будут давать возможность начинать защищённое соединение. При этом — принимать такие соединения бесплатно.
gmail наконец монетизирует своих пользователей. За 10$ в 3 года — право создавать защищённые каналы переписки.

============= Заключение =============


Для тестирования всей статьи я собирался арендовать выделенный сервер на месяц и купить домен с ssl сертификатом.

Но жизненные обстоятельства сложились так этот вопрос затянулся на 2 месяца.
И вот когда появилось снова свободное время — решил публиковать статью как есть, а не рисковать тем, что публикация затянется ещё на год.

Если будет достаточно много вопросов типа «а вот тут не достаточно подробно описано» — тогда наверное найдутся силы таки взять выделенный сервер с новым доменом и новым SSL сертификатом и ещё подробнее описать и главное — выявить все упущенные важные детали.

Также хотелось бы получить отзывы на тему идеи про почтовые сертификаты. Если идея понравится — постараюсь найти силы написать черновик для rfc.

При копировании больших кусков статьи — указывать ссылку на эту статью.
При переводе на любой другой язык — указывать ссылку на эту статью.
На английский язык я сам постараюсь перевести и оставлю перекрёстные ссылки.

Комментарии (31)


  1. neenik
    07.04.2019 01:04
    +1

    Посмотрите mailcow.


  1. in_heb
    07.04.2019 01:21
    +4

    Почитайте RFC 5737, 3849. Не очень удобно читать howto, когда ip-адреса имеют вид xx.xx.xx.xx
    Зря вы добавили openvpn в статью. К почте он отношения не имеет. В итоге получалась какая-то каша из настройки маршрутизации, vpn-а и почты. Хотя бы пометьте какие настройки не нужны, если не нужен vpn.


    1. podivilov
      07.04.2019 16:50

      Тоже статья показалось несколько странной. Если уж и рассказывать про настройку почтового сервера, то про самый распространённый кейс, подходящий многим. А вот что до слов автора «В статье отсутствуют попытки пояснить каждую деталь [...]» — очень жаль. В случае с почтой обычно никакие подробности опускать нельзя, потому как дело это весьма хитрое и замысловатое.


    1. anlide Автор
      07.04.2019 18:40

      Это конкретный случай, когда в системе есть несколько доменов и несколько физических интерфейсов. Поэтому про маршрутизацию было важно рассказать.

      Почему я решил включить openvpn? Потому что в РФ нет возможности получить IPv6 без проблем на конечную машину. А как тестировать IPv6 без openvpn в такой ситуации? Никак. Потому и включил его в статью.

      Спасибо за ссылки на RFC.


      1. in_heb
        07.04.2019 19:28
        +1

        Просто писать про vpn в статье про почту это жуткий оверкилл.
        В РФ нативно даёт Эртелеком и МТС (на мобильной сети), т.е. получить нативный ipv6 не так уж и сложно.
        Ну ладно, допустим у вашего isp его нет. Но есть же туннельные брокеры https://en.m.wikipedia.org/wiki/List_of_IPv6_tunnel_brokers (для тестов это более чем достаточно)
        Допустим, по какой-то причине и это не вариант. Тогда берёте самую дешёвую vps под это дело (рекомендую использовать lowendbox/lowendtalk для поиска) и тестирует с неё, а то у вас даже тест не совсем честный, по сути внутри хоста тестируете часть вещей.


  1. wtpltd
    07.04.2019 02:06

    Таки, если необходимо, выделенный сервер предоставить готов. И выделенный домен. Что касается сертификатов — как на счет летсенкриптов?
    И, если уж заморачиваться с собственным почтовиком, так это или здоровая параноя или корпоративные фишки, которые никто предоставить не может.
    Сможете запилить и я буду первым подписчиком вашего стартапа :)


    1. anlide Автор
      07.04.2019 18:35

      Финансово легко сам потяну. Времени резко меньше стало свободного в последнее время.

      Собственный почтовый сервер имеет смысл ставить в корпоративных целях. Или когда вам катастрофически важна гарантия доставки писем и/или максимальный антиспам рейтинг.


      1. in_heb
        07.04.2019 19:32

        Это всё до первого ддос и прочих атак, а потом вы начинаете прятаться за антиддос сервис (что вносит огромные коррективы ко всей схеме) или уходите на публичные провайдеры типа Microsoft (office365)


  1. KAndy
    07.04.2019 05:12
    -4

    Вот меня удивляет, что люди в 2019 году, по-прежнему, запускают сервисы не в docker/kuberntes


    1. mrobespierre
      07.04.2019 06:09
      +4

      Все люди разные. Некоторым нужны безопасность, стабильность, надёжность и производительность, которых Docker с его тысячами открытых issues предоставить не может. А ещё некторые понимают, что Docker — средство доставки приложений. Если ваш стартап выкатывает новую версию сервиса 2 раза в неделю, Docker незаменим. Если вы поднимаете почтовый сервак, который будет работать годами Docker вообще ни к чему.


    1. immaculate
      07.04.2019 12:26
      +1

      Помимо всего, что уже написали, работать с Docker банально менее удобно. И непонятно зачем добавлять лишние сущности и уровни абстракции в данном конкретном случае. Кроме лишней возни это ничего не добавит.


      Ну и бонусом идет, например, невозможность безопасной настройки LetsEncrypt в докере. Все решения, которые я видел, реализованы с открытием лазейки позволяющей выходить за пределы контейнера.


  1. rrpv
    07.04.2019 05:55
    +2

    А это самое интересное. Это возможность отправлять письма для конкретного домена с конкретного IPv4/IPv6 адреса.

    Если helo не соответствует домену почты, от имени кого отправили письмо — начисляются спам очки.

    Сколько можно распространять по интернету эту глупость?
    Кто-то один раз где-то это написал, и теперь каждый натыкающийся на эту "кладезь мудрости" старается внедрить её у себя и рассказать миру об этом достижении.


    HELO не обязано соответствовать домену адреса отправителя, и никакие известные мне почтовые антиспамы за это штрафные баллы не начисляют.


    Соответственно для каждого домена должен быть свой IP адрес.

    Такого требования также нет.


    1. anlide Автор
      07.04.2019 18:32

      Почтовые сервера на outlook.com всегда делают bounce письмам отправленным с сервера, у которого неправильный HELO.
      spamassassin, который установлен у многих почтовых серверов, по умолчанию даёт 1.6 очков спама на неправильный HELO.

      Так-то да, если вам не важно попадёт письмо в спам или нет — то на HELO можно не обращать внимание.


      1. VitalKoshalew
        07.04.2019 21:02

        Вы путаете «неправильный» адрес в HELO (который не соответствует прямому и обратному DNS) и то, что у автора статьи — «helo не соответствует домену почты, от имени кого отправили письмо». Последнее — выдумка, потому что тот же outlook.com шлёт почту от имени тысяч доменов, нет и не может быть такого ограничения. Соответствие записям SPF/DMARC домена-отправителя могут проверяться, а сравнивать домен одного из почтовых серверов в цепочке почему-то с адресом FROM в письме, я не знаю, кому в голову может прийти такая идея, видимо это urban legend.


        1. konchok
          07.04.2019 21:15

          В идеале HELLO должен точно соответствовать MX записи, reverse DNS этого IP и имени хоста в SSL сертификате — спам-фильтры такому очень радуются. Какой-то аутлук это не показатель ничего вообще.


  1. fluffybear
    07.04.2019 08:21

    Вместо связки OpenDKIM + SpamAssasin не смотрели в сторону Rspamd? Шустрый, работает «из коробки» с минимальными настройками.


  1. neol
    07.04.2019 09:32

    Также хотелось бы получить отзывы на тему идеи про почтовые сертификаты. Если идея понравится — постараюсь найти силы написать черновик для rfc.

    Его уже написали, одобрили и чёрт знает сколько лет успешно используют: tools.ietf.org/html/rfc4880


    1. anlide Автор
      07.04.2019 18:45

      Я в курсе про PGP. Но пользоваться им неудобно, уж простите.

      Нужен какой-то новый стандарт, при помощи которого в один клик пользователи могли бы установить защищённое соединение. На уровне клиентов. И чтобы это было бы массово распространено среди всех существующих почтовых клиентов.


      1. darken99
        07.04.2019 19:16

        Это кроме параноиков или людей занимающихся не совсем законной деятельностью нафиг никому не надо. А кому надо тот и PGP освоит.


      1. neol
        07.04.2019 19:18

        image

        PGP за почти 3 десятка лет существования не получил распространения среди всех существующих почтовых клиентов ( хотя на мой взгляд в Evolution или Thunderbird пользоваться им достаточно легко ), а вы надеетесь вот так просто взять и договориться с Microsoft, Google, IBM, Apple и кучей компаний поменьше? Да вам сказки надо писать, а не RFC.


  1. pae174
    07.04.2019 11:06
    +1

    > Только спустя 8 лет после начала моей карьеры я стал понимать как работает SSL.
    >…
    > Веб сервер отдаёт публичный ключ. Браузер используя публичный ключ зашифровывает
    > http-request и отправляет его.

    8 лет оказалось недостаточно.


    1. 1c80
      07.04.2019 11:24

      Можете ли пояснить, что не так?


      1. pae174
        07.04.2019 14:01
        +1

        Всё не так. Эта ваша желтая секция про HTTPS содержит набор ереси. Там просто через сстрочку можно писать ответ «это не так, читайте вики.»

        > Браузер используя публичный ключ зашифровывает http-request и отправляет его.
        Это не так, читайте вики: TLS.

        > если в стране пытаются ограничить доступ не ко всему сайту,
        > а к конкретной странице — то для https сайтов это сделать невозможно.
        Очень даже возможно. Для этого государству надо либо вступить в сговор с CA (wiki: StartCom), либо самому стать CA ( habr.com/ru/post/272207 ), либо заюзать что-то типа Carnivore или NarusInsight (но это не точно, агентство которого нет всё отрицает а вики врет). Хотя, если вы король какой-нибудь банановой республики или царек в Рога и Копыта Интернешнл, то вы можете так не заморачиваться а просто связаться с Symantec Solutions и они вам помогут с перехватом HTTPS трафика всех этих ваших холопов.

        > браузер у себя локально формирует такую-же пару приватный-публичный ключ
        > для каждого https сайта
        Не фомирует браузер никакую такую пару ключей.

        > И вместе с запросом публичного ключа сайта отправляет свой локальный публичный ключ.
        wiki: TLS. Ну или вы неправильно поняли аутентификацию по сертификатам, которая к тому же на публичных сайтах всё равно не применяется.

        > http-response шифрует этим вот публичным ключём конкретного клиента.
        wiki: TLS, сеансовый ключ там используется на самом деле а не публичный.

        Вместо ожидаемого упоминания Диффи-Хеллмана написана какая-то ерунда.

        > Само создание такого безопасного канала связи происходит со скоростью ping*2
        Два пинга будет только если не учитывать TCP handshake (+1 пинг), не смотреть в CRL (много пингов), не дергать никого по OSCP (много-много пингов). Ну то есть если тупо не проверять совсем ничего и всегда слепо доверять всем без разбору — тогда да, парой-тройкой пингов можно обойтись. Хотя OCSP stapling устраняет эти вот многочисленные лишние пинги. Но его используют только 30% хостов примерно, а у вас он так и вообще не упомянут.

        > Для борьбы с такими злоумышленниками придумали публичную БД
        > с публичными ключами для каждого https сайта.
        Вы, наверное, CA имели в виду. Там у них нет никакой публичной базы публичных ключей. Если CRL — публичные списки отозванных сертификатов — то есть таких, которые раньше можно было использовать а теперь нельзя. Есть OSCP, но это запрос-ответ а не скачивание базы чего-либо.

        > Ещё добавлю про ssh соединения. Там никаких публичных ключей нет.
        Да ну?

        > При первом соединении ssh-клиент должен предупредить, что тут у нас новый публичный ключ.
        Вы же только что написали, что никаких публичных ключей нет.

        > Через пару часов мы получаем от этой сторонней компании наш «публичный» ключ и ещё набор нескольких публичных ключей.
        Сертификат вы оттуда получаете подписанный а вовсе не ключ. Это разные вещи.

        > chained — обозначение, что тут цепочка публичных ключей
        Цепочка сертификатов. Не бывает никаких цепочек ключей в природе.


        1. 1c80
          07.04.2019 14:09

          ну блин..., спасибо за информацию.
          оказалось что всё еще на порядок сложнее и как правильно настроить это добро даже и приблизительно не понятно теперь, как же правильно то сделать?


        1. anlide Автор
          07.04.2019 18:18

          Спасибо за развёрнутый комментарий. Я хотел донести идеи, очень простым языком. Даже упуская важные детали.


          1. rrpv
            08.04.2019 07:13
            +1

            Ваша статья требует полной переработки, с учетом вышеизложенных в комментариях замечаний. Несмотря на наличие этих замечаний, никаких изменений в содержимое статьи Вами не внесено, и она продолжает нести в массы дезинформацию и неверные тезисы.


            Я бы попросил Вас скрыть её в черновики до момента переработки содержимого.
            Вы уже получили много отзывов и Вам есть над чем подумать.


  1. roller
    07.04.2019 12:37
    +2

    А где ansible-плейбук? Или вы руками предлагаете все это натыкивать во второй раз. Сейчас 2k19 на минуточку, без плейбука зачет не приму!


    1. xaerowalk
      09.04.2019 17:21

      в 2к19 где контейнер, а не плейбук


  1. pae174
    07.04.2019 13:56

    del


  1. asoloviov
    07.04.2019 14:10

    Если уж включать spam паранойю, то я бы добавил еще:


    • поддержку DMARC и ARC
    • дополнительные и/или свои блеклисты (это отсылка к строке reject_rbl_client sbl.spamhaus.org,)
    • например fail2ban с реакцией на логи postfix о несовпадении hostname<->rDNS, невалидные сертификаты, оборванные входящие TLS/SSL сессии

    Ну и плюсую за OpenPGP aka RFC4880


  1. konchok
    07.04.2019 19:06

    Вместо костыльного spamassassin имеется приятный и технологичный rspamd. Он и DKIM умеет из коробки. Непонятно зачем тут нужнен OpenVPN лучше бы рассмотрели веб интерфейс для почты.
    Ну и напоследок — есть же www.iredmail.org, вся связка уже готовая и настроеная.