воскресенье, 20 апреля 2014 г.

Настройка Exim

На сей раз я опишу альтернативный вариант настройки ранее описанной мной почтовой системы, в которой место Postfix займёт Exim.

Ранее, где-то в начале 2008 года, я уже настраивал почтовую систему на основе Exim. С тех пор я успел напрочь забыть всю конкретику, но у меня осталось благоприятное впечатление об этом почтовом сервере. Этот почтовый сервер оказался настолько гибким в настройке, что я бы скорее назвал его языком программирования почтовых систем, а не почтовым сервером :)

Эти системы отличаются по архитектуре и целям. Postfix исповедует подход Unix - состоит из небольших взаимодействующих между собой программ. Большое внимание в Postfix уделяется безопасности и надёжности. Каждая программа решает небольшую задачу и использует минимум необходимых прав, зачастую работая в chroot. Есть центральный процесс-диспетчер, который при необходимости перезапустит неожиданно завершившуюся программу. Процесс-диспетчер периодически перезапускает и нормально работающие программы, чтобы защититься от потенциальных утечек памяти. В Postfix есть множество настроек на разные случаи жизни, однако если нужной настройки нет, то скорее всего без дополнительной программы не обойтись.

Exim концентрируется на богатстве возможностей обработки почты. У него тоже есть процесс-диспетчер, но процессы не настолько узко специализированы и не запускаются в chroot. Он менее безопасен по дизайну, однако написан довольно аккуратно. Поэтому на практике он не сильно проигрывает в безопасности. Но и рисковать лишний раз не стоит: если вам не нужно чего-то особого и хватает возможностей Postfix, я бы порекомендовал пользоваться Postfix. У Exim нет жёстко фиксированных настроек, в большинстве случаев необходимое поведение можно сконструировать, используя различные встроенные функции.

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

С переводами документации связана неприятная история. Есть два человека, каждый из которых считает себя настоящим автором перевода, обвиняя другого в воровстве. Первый - Алекс Кеда, Exim 4.70 и второй - Алексей Паутов, Exim 4.80. Я долго изучал оба перевода, искал различные варианты в сети, изучал содержимое web.archive.org с целью понять, чем отличаются переводы и кому принадлежит первенство. Оказалось, что переводы имеют общую основу, отличаясь небольшими деталями. Перевод, сделанный Алексом Кеда, был довольно неуклюжим, но появился в сети в 2007 году. Более того - по веб-архиву можно даже заметить, что он появлялся поэтапно. На сайте Алексея Паутова перевод появился целиком в 2008 году, но был хорошо отредактирован. Конечно, на основании данных одного лишь вебархива нельзя делать далеко идущие выводы. Может быть Алексей Паутов ранее размещал перевод на каком-то другом сайте, упоминания которого сейчас в интернете исчезли полностью. Но если не усложнять, то автором оригинала определённо был Алекс Кеда. Однако, на текущий момент более актуальный и аккуратный перевод можно найти на сайте Алексея Паутова.

1. Установка

Установим SMTP-сервер Exim:
# apt-get install exim4-daemon-heavy
Я решил не пользоваться системой конфигурации Exim, которая принята Debian. В ней нет некоторых частей конфигурации, которые мне понадобятся. Добавить их не проблема, однако придётся ещё исправлять другие файлы, идущие в дистрибутиве, чтобы отключить их обработку в случае использования необходимых мне файлов. А это уже показалось мне неоправданной тратой времени - много хлопот без явной выгоды.

К счастью, каких-то особых действий для отключения этой системы конфигурирования не требуется - достаточно просто создать файл /etc/exim4/exim4.conf, в который и вписать необходимые настройки. В качестве прототипа этого файла я воспользовался информацией из документации: Chapter 7 - The default configuration file. Для интеграции Exim и Dovecot я воспользовался информацией из wiki-системы Dovecot: Dovecot LDA with Exim, Exim and Dovecot SASL. Для интеграции Exim и базы данных Postfixadmin я воспользовался своей предыдущей заметкой Установка и настройка Postfix, OpenDKIM, ClamAV-Milter, Milter-Greylist.

Получился такой файл /etc/exim4/exim4.conf:
# Имя нашей почтовой системы
primary_hostname = mail.domain.tld

# База данных MySQL и учётные данные для работы с ней
hide mysql_servers = localhost/postfixadmin/exim/exim_password

# Список доменов нашей почтовой системы
domainlist local_domains = ${lookup mysql{SELECT domain \
                                          FROM domain \
                                          WHERE domain = '${quote_mysql:$domain}' \
                                            AND backupmx = 0 \
                                            AND active = 1}}

# Список доменов, для которых наша почтовая система является резервной
domainlist relay_domains = ${lookup mysql{SELECT domain \
                                          FROM domain \
                                          WHERE domain = '${quote_mysql:$domain}' \
                                            AND backupmx = 1 \
                                            AND active = 1}}
# Список узлов, почту от которых будем принимать без проверок
hostlist relay_from_hosts = 

# Правила для проверок
acl_not_smtp = acl_check_not_smtp
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data

# Сокет-файл антивируса ClamAV
av_scanner = clamd:/var/run/clamav/clamd.ctl
# Сокет-файл SpamAssassin
# spamd_address =

# Отключаем IPv6, слушаем порты 25 и 587
disable_ipv6
daemon_smtp_ports = 25 : 587

# Дописываем домены отправителя и получателя, если они не указаны
qualify_domain = domain.tld
qualify_recipient = domain.tld

# Exim никогда не должен запускать процессы от имени пользователя root
never_users = root

# Проверять прямую и обратную записи узла отправителя по DNS
host_lookup = *

# Отключаем проверку пользователей узла отправителя по протоколу ident
rfc1413_hosts = *
rfc1413_query_timeout = 0s

# Только эти узлы могут не указывать домен отправителя или получателя
sender_unqualified_hosts = +relay_from_hosts
recipient_unqualified_hosts = +relay_from_hosts

# Лимит размера сообщения, 30 мегабайт
message_size_limit = 30M

# Запрещаем использовать знак % для явной маршрутизации почты
percent_hack_domains =

# Настройки обработки ошибок доставки, используются значения по умолчанию
ignore_bounce_errors_after = 2d
timeout_frozen_after = 7d

begin acl

  # Проверки для локальных отправителей
  acl_check_not_smtp:
     accept

  # Проверки на этапе RCPT
  acl_check_rcpt:
    accept hosts = :

    # Отклоняем неправильные адреса почтовых ящиков  
    deny message = Restricted characters in address
         domains = +local_domains
         local_parts = ^[.] : ^.*[@%!/|]

    # Отклоняем неправильные адреса почтовых ящиков  
    deny message = Restricted characters in address
         domains = !+local_domains
         local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./

    # В локальные ящики postmaster и abuse принимает почту всегда
    accept local_parts = postmaster : abuse
           domains = +local_domains

    # Проверяем существование домена отправителя
    require verify = sender

    # Принимаем почту от доверенных узлов, попутно исправляя заголовки письма
    accept hosts = +relay_from_hosts
           control = submission

    # Принимаем почту от аутентифицированных узлов, попутно исправляя заголовки письма
    accept authenticated = *
           control = submission/domain=

    # Для не доверенных и не аутентифицированных требуется, чтобы получатель был в домене,
    # ящик которого находится у нас или для которого мы являемся резервным почтовым сервером
    require message = Relay not permitted
            domains = +local_domains : +relay_domains

    # Если домен правильный, то проверяем получателя
    require verify = recipient

    accept

begin routers

  # Поиск транспорта для удалённых получателей
  dnslookup:
    driver = dnslookup
    domains = ! +local_domains
    transport = remote_smtp
    ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
    no_more

  # Пересылки для локальных получателей из файла /etc/aliases
  system_aliases:
    driver = redirect
    allow_fail
    allow_defer
    domains = domain.tld
    data = ${lookup{$local_part}lsearch{/etc/aliases}}

  # Пересылки с ящика на ящик в локальных доменах из Postfixadmin
  aliases:
    driver = redirect
    allow_fail
    allow_defer
    data = ${lookup mysql{SELECT LCASE(goto) \
                          FROM alias \
                          WHERE address = LCASE('${quote_mysql:$local_part@$domain}') \
                            AND active = 1}}

  # Пересылки на одноимённые ящики в другом домене из Postfixadmin
  alias_domain:
    driver = redirect
    allow_fail
    allow_defer
    data = ${lookup mysql{SELECT alias.goto \
                          FROM alias_domain \
                          JOIN alias ON alias.address = LCASE('${quote_mysql:$local_part@$domain}') \
                            AND alias.active = 1 \
                          WHERE alias_domain.alias_domain = LCASE('${quote_mysql:$domain}') \
                            AND alias_domain.active = 1}}

  # Пересылки на ящик по умолчанию в локальном домене из Postfixadmin
  alias_domain_catchall:
    driver = redirect
    allow_fail
    allow_defer
    data = ${lookup mysql{SELECT alias.goto \
                          FROM alias_domain \
                          JOIN alias ON alias.address = LCASE('${quote_mysql:@$domain}') \
                            AND alias.active = 1 \
                          WHERE alias_domain.alias_domain = LCASE('${quote_mysql:$domain}') \
                            AND alias_domain.active = 1}}
  
  # Получение почты на локальный ящик из Postfixadmin
  mailbox:
    driver = accept
    condition = ${lookup mysql{SELECT maildir \
                               FROM mailbox \
                               WHERE username = LCASE('${quote_mysql:$local_part@$domain}') \
                                 AND active = 1}{yes}{no}}
    transport = dovecot_virtual_delivery

  # Получение почты на локальный ящик с альтернативным доменным именем из Postfixadmin
  alias_domain_mailbox:
    driver = accept
    condition = ${lookup mysql{SELECT mailbox.maildir \
                               FROM alias_domain \
                               JOIN mailbox ON mailbox.local_part = LCASE('${quote_mysql:$local_part}') \
                                 AND mailbox.domain = alias_domain.target_domain \
                                 AND mailbox.active = 1 \
                               WHERE alias_domain.alias_domain = LCASE('${quote_mysql:$domain}') \
                                 AND alias_domain.active = 1}{yes}{no}}
    transport = dovecot_virtual_delivery
    cannot_route_message = Unknown user

begin transports

  # Транспорт для удалённых получателей
  remote_smtp:
    driver = smtp

  # Транспорт для локальных получателей из Dovecot
  dovecot_virtual_delivery:
    driver = pipe
    command = /usr/lib/dovecot/dovecot-lda -d $local_part@$domain -f $sender_address
    message_prefix =
    message_suffix =
    delivery_date_add
    envelope_to_add
    return_path_add
    log_output
    user = vmail
    temp_errors = 64 : 69 : 70: 71 : 72 : 73 : 74 : 75 : 78

begin retry

  *   *   F,2h,15m; G,16h,1h,1.5; F,4d,6h

begin rewrite

begin authenticators

  # Использование LOGIN-аутентификации из Dovecot
  dovecot_login:
    driver = dovecot
    public_name = LOGIN
    server_socket = /var/run/dovecot/auth-client
    server_set_id = $auth1

  # Использование PLAIN-аутентификации из Dovecot  
  dovecot_plain:
    driver = dovecot
    public_name = PLAIN
    server_socket = /var/run/dovecot/auth-client
    server_set_id = $auth1
Сразу поменяем права доступа к файлу конфигурации, чтобы обычные пользователи системы не смогли увидеть пароль доступа к базе данных:
# chmod u=rw,g=r,o= /etc/exim4/exim4.conf
# chown root:Debian-exim /etc/exim4/exim4.conf
Для того, чтобы exim мог извлекать данные из базы данных Postfixadmin, добавим пользователя exim:
USE mysql;

INSERT INTO user(user, password, host) VALUES('exim', PASSWORD('exim_password'), 'localhost');

FLUSH PRIVILEGES;
Дадим пользователю exim доступ к таблице ящиков, доменов, псевдонимов, псевдонимов доменов и квот:
USE mysql;

INSERT INTO tables_priv(host, db, user, table_name, table_priv, column_priv) VALUES
('localhost', 'postfixadmin', 'exim', 'alias', '', 'Select'),
('localhost', 'postfixadmin', 'exim', 'alias_domain', '', 'Select'),
('localhost', 'postfixadmin', 'exim', 'mailbox', '', 'Select'),
('localhost', 'postfixadmin', 'exim', 'domain', '', 'Select'),
('localhost', 'postfixadmin', 'exim', 'quota2', '', 'Select');

INSERT INTO columns_priv(host, db, user, table_name, column_name, column_priv) VALUES
('localhost', 'postfixadmin', 'exim', 'alias', 'goto', 'Select'),
('localhost', 'postfixadmin', 'exim', 'alias', 'address', 'Select'),
('localhost', 'postfixadmin', 'exim', 'alias', 'active', 'Select');

INSERT INTO columns_priv(host, db, user, table_name, column_name, column_priv) VALUES
('localhost', 'postfixadmin', 'exim', 'alias_domain', 'target_domain', 'Select'),
('localhost', 'postfixadmin', 'exim', 'alias_domain', 'alias_domain', 'Select'),
('localhost', 'postfixadmin', 'exim', 'alias_domain', 'active', 'Select');

INSERT INTO columns_priv(host, db, user, table_name, column_name, column_priv) VALUES
('localhost', 'postfixadmin', 'exim', 'mailbox', 'maildir', 'Select'),
('localhost', 'postfixadmin', 'exim', 'mailbox', 'username', 'Select'),
('localhost', 'postfixadmin', 'exim', 'mailbox', 'active', 'Select'),
('localhost', 'postfixadmin', 'exim', 'mailbox', 'quota', 'Select'),
('localhost', 'postfixadmin', 'exim', 'mailbox', 'local_part', 'Select'),
('localhost', 'postfixadmin', 'exim', 'mailbox', 'domain', 'Select');

INSERT INTO columns_priv(host, db, user, table_name, column_name, column_priv) VALUES
('localhost', 'postfixadmin', 'exim', 'domain', 'domain', 'Select'),
('localhost', 'postfixadmin', 'exim', 'domain', 'backupmx', 'Select'),
('localhost', 'postfixadmin', 'exim', 'domain', 'active', 'Select');

INSERT INTO columns_priv(host, db, user, table_name, column_name, column_priv) VALUES
('localhost', 'postfixadmin', 'exim', 'quota2', 'username', 'Select'),
('localhost', 'postfixadmin', 'exim', 'quota2', 'bytes', 'Select');

FLUSH PRIVILEGES;
Чтобы Exim мог использовать механизмы аутентификации из Dovecot, нужно вписать в файл /etc/dovecot/conf.d/10-master.conf, в секцию service auth следующие настройки:
unix_listener auth-client {
  mode = 0660
  user = Debian-exim
}
Осталось перезапустить Exim и Dovecot, чтобы Exim начали работать в минимальной конфигурации:
# /etc/init.d/dovecot restart
# /etc/init.d/exim4 restart
2. Настройка антивируса

Устанавливаем демон ClamAV для проверки файлов на вирусы:
# apt-get install clamav-daemon
Сразу же обновляем антивирусную базу:
# freshclam
Включим clamav в группу Debian-exim, чтобы он мог сканировать файлы, созданные Exim'ом:
# usermod -aG Debian-exim clamav
Добавим в главную секцию файла /etc/exim4/exim4.conf путь к сокет-файлу ClamAV и ACL для этапа DATA:
av_scanner = clamd:/var/run/clamav/clamd.ctl
acl_smtp_data = acl_check_data
В секцию acl файла /etc/exim4/exim4.conf добавим правило, запрещающее приём писем, содержащих вирусы:
acl_check_data:

  deny message = message contains a virus ($malware_name)
       malware = *

  accept
Перезагрузим Exim, чтобы настройки вступили в силу:
# /etc/init.d/exim4 reload
Осталось проверить, что антивирусная система используется. Для этого создадим специально предназначенный для таких целей тестовый файл EICAR:
$ echo -n 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > eicar.txt
И попробуем его отправить во вложении с какого-нибудь почтового ящика почтовой системы на тот же ящик. Если письмо не пришло, значит антивирусная система работает. Для полной уверенности можно ещё заглянуть в журнал почтовой системы /var/log/exim4/mainlog, где должна появиться строчка вида:
2014-04-12 22:06:06 1WZ0RR-0007TO-RM H=localhost (domain.tld) [127.0.0.1] F= A=dovecot_plain:box@domain.tld rejected after DATA: message contains a virus (Eicar-Test-Signature)
3. Проверка квот

Добавим в файл /etc/exim4/exim4.conf в самый конец ACL acl_check_rcpt, но перед финальным правилом accept, следующие правила:
discard message = 422 Mailbox $local_part@$domain is over quota
        domains = +local_domains
        condition = ${lookup mysql{SELECT 1 \
                                   FROM mailbox \
                                   JOIN quota2 ON quota2.username = mailbox.username \
                                     AND quota2.bytes + ${if ={$message_size}{-1}{${expand:message_size_limit}}{$message_size}} >= mailbox.quota \
                                   WHERE mailbox.username = LCASE('${quote_mysql:$local_part@$domain}') \
                                     AND mailbox.active = 1}}

discard message = 422 Mailbox $local_part@$domain is over quota
        domains = +local_domains
        condition = ${lookup mysql{SELECT 1 \
                                   FROM alias_domain \
                                   JOIN mailbox ON mailbox.local_part = LCASE('${quote_mysql:$local_part}') \
                                     AND mailbox.domain = alias_domain.target_domain \
                                     AND mailbox.active = 1 \
                                   JOIN quota2 ON quota2.username = mailbox.username \
                                     AND quota2.bytes + ${if ={$message_size}{-1}{${expand:message_size_limit}}{$message_size}} >= mailbox.quota \
                                   WHERE alias_domain.alias_domain = LCASE('${quote_mysql:$domain}') \
                                     AND alias_domain.active = 1}}
Эти правила используют размер сообщения, анонсированного системой отправителя на этапе MAIL FROM, чтобы проверить превышение квоты почтового ящика локального получателя. Если на этапе MAIL FROM не был анонсирован размер сообщения, то для проверки квоты используется максимальный размер сообщения, который может быть принят почтовой системой. Правило discard позволяет не отклонять сообщение полностью, а только лишь отказаться от одного из получателей, если его квота будет превышена. Если письмо адресовано нескольким получателям, то письмо будет принято только для тех получателей, у которых квота не превысится.

Тут стоит отметить, что окончательный размер письма может оказаться другим. Например, наш почтовый сервер добавит к письму несколько заголовков, в результате чего письмо получится немного больше. Отправитель может случайно или намеренно указать размер письма, не соответствующий действительности. На этапе DATA, когда известен точный размер письма, можно отказаться от всего письма целиком, но не от отдельных его получателей, что мне кажется неправильным. Поэтому я не проверяю квоты на этапе DATA, а полагаюсь на обработку ошибок Dovecot'ом, который инициирует отправку писем об ошибке доставки в адрес отправителя.

Осталось перезагрузить Exim, чтобы новые правила вступили в силу:
# /etc/init.d/exim4 reload
4. Настройка SSL/TLS

Изменим настройки прослушиваемых портов в главной секции файла /etc/exim4/exim4.conf, добавив в список порт 465:
daemon_smtp_ports = 25 : 465 : 587
Добавим настройки TLS в главную секцию файла /etc/exim4/exim4.conf:
tls_on_connect_ports = 465
tls_advertise_hosts = *
tls_certificate = /etc/ssl/mail.domain.tld.public.pem
tls_privatekey = /etc/ssl/mail.domain.tld.private.pem
Порт 465 используется для подключения сразу по шифрованному каналу SSL, без явного согласования шифрования. Порт 587 обычно используется для подключений со стороны почтовых клиентов, как правило, с обязательным использованием аутентификации. В рассматриваемой конфигурации порты 25 и 587 никак не различаются, поведение сервера одинаково на обоих портах.

Для задействования добавленных настроек TLS, перезапустим почтовый сервер:
# /etc/init.d/exim4 restart
5. Настройка DKIM-подписей

Для удобного создания ключей DKIM-подписей можно установить пакет opendkim-tools:
# apt-get install opendkim-tools
На самом деле необходимые ключи можно генерировать и при помощи openssl, т.к. пакет opendkim-tools содержит набор shell-скриптов, являющихся обёрткой над утилитой openssl.

Теперь создадим каталог для ключей и сгенерируем пару ключей для домена domain.tld:
# mkdir /etc/exim4/dkim
# cd /etc/exim4/dkim
# opendkim-genkey -D /etc/exim4/dkim/ -d domain.tld -s mail
# mv mail.private mail.domain.tld.private
# mv mail.txt mail.domain.tld.public
Далее можно сгенерировать ключи для других доменов, обслуживаемых нашей почтовой системой.

Выставим права доступа к файлам приватных ключей:
# cd /etc/exim4/dkim
# chmod u=rw,g=r,o= *
# chown root:Debian-exim *
Добавляем в секцию транспортов файла /etc/exim4/exim4.conf, в транспорт remote_smtp, настройки для добавления DKIM-подписей к письмам:
remote_smtp:
  driver = smtp
  dkim_domain = ${lc:${domain:$h_from:}}
  dkim_selector = mail
  dkim_private_key = ${if exists{/etc/exim4/dkim/$dkim_selector.$dkim_domain.private} \
                                {/etc/exim4/dkim/$dkim_selector.$dkim_domain.private}{}}
Достаточно перезагрузить конфигурацию, чтобы письма во внешние домены начали подписываться DKIM-ключами:
# /etc/init.d/exim4 reload
6. Проверка DKIM-подписей

Воспользуемся встроенной в Exim возможностью проверки DKIM-подписей входящих писем. Я буду проверять подписи у тех писем, в которых они есть. Плюс к тому, будем требовать наличия правильной DKIM-подписи для доменов публичных почтовых сервисов, о которых заведомо известно, что они добавляют DKIM-подписи к своим письмам. Это позволит защититься от поддельных писем, якобы исходящих из доменов этих почтовых сервисов.

Зададим в главной секции файла /etc/exim4/exim4.conf домены, для которых требуется правильная DKIM-подпись:
domainlist dkim_required_domains = gmail.com : yandex.ru : rambler.ru : \
                                   mail.ru : bk.ru : list.ru : inbox.ru
В эту же главную секцию файла /etc/exim4/exim4.conf добавим имя списка управления доступом, который будет проверять DKIM-подпись:
acl_smtp_dkim = acl_check_dkim
В секцию acl файла /etc/exim4/exim4.conf добавим описание самого списка управления доступом:
acl_check_dkim:

  # Отклоняем письма с неправильной DKIM-подписью
  deny message = Wrong DKIM signature 
       dkim_status = fail

  # Для выбранных доменов требуем наличия DKIM-подписи
  deny message = Valid DKIM signature needed for mail from $sender_domain
       sender_domains = +dkim_required_domains
       dkim_status = none

  accept
Перезагрузим файл конфигурации Exim, чтобы настройки вступили в силу:
# /etc/init.d/exim4 reload
7. Настройка грейлистинга

Для грейлистинга воспользуемся демоном greylistd, написанном на Python. Этот демон не настолько сложен, как milter-greylist, которым я воспользовался для настройки грейлистинга в Postfix, однако его простота с лихвой компенсируется возможностями Exim. Установим пакет greylistd:
# apt-get install greylistd
greylistd предоставляет механизм, а политику можно определить в конфигурации Exim. Я придерживаюсь политики подвергать грейлистингу те узлы, которые оказались в чёрном списке. Для того, чтобы включить грейлистинг, нужно в самый конец списка управления доступом acl_check_rcpt до финального правила accept добавить следующую проверку:
defer message = Greylisting in action, try later
      !senders = :
      !hosts = ${if exists{/etc/greylistd/whitelist-hosts}\
                          {/etc/greylistd/whitelist-hosts}{}} : \
               ${if exists{/var/lib/greylistd/whitelist-hosts}\
                          {/var/lib/greylistd/whitelist-hosts}{}}
      dnslists = zen.spamhaus.org
      condition = ${readsocket{/var/run/greylistd/socket}\
                              {--grey $sender_host_address $sender_address $local_part@$domain}\
                              {5s}{}{false}}
В поле !senders можно прописать адреса тех отправителей, которые не должны подвергаться грейлистингу. Соответственно, чтобы узел с определённым IP-адресом не подвергался грейлистингу, его можно добавить в файл /etc/greylistd/whitelist-hosts.

Осталось попросить Exim перезагрузить файл конфигурации, чтобы новые настройки вступили в силу:
# /etc/init.d/exim4 reload
8. Настройка SPF-записи

SPF-запись - это TXT-запись следующего вида:
domain.tld. IN TXT "v=spf1 +mx ~all"
Если указанный домен обслуживает Sender Policy Framework, описывающему синтаксис SFP-записи - SPF Record Syntax. Стоит также прочесть о наиболее частых ошибках, допускаемых при создании SFP-записи - Common mistakes.

9. Проверка SPF-записей

Имеются разные способы проверки SPF-записей почтовой системой Exim. Сейчас в официальном дитрибутиве Debian поставлюятся конфигурационные файлы, проверяющие SPF-записи при помощи Perl-программы из пакета libmail-spf-perl. При этом каждая проверка инициирует новый запуск программы. На мой взгляд это довольно расточительно. Ранее существовал пакет libmail-spf-query-perl, в составе которого имелся демон, к которому можно было обратиться через Unix-сокет. Этот способ уже гораздо лучше и по сути ничем не отличается от грейлистинга при помощи демона greylistd на Python'е. Однако сейчас этот пакет не поставляется в репозитории Debian и, похоже, вообще не поддерживается авторами.

Имеется ещё один способ проверки SPF-записей - при помощи самого Exim. Однако эта опция считается экспериментальной и поэтому отключена по умолчанию. Пакеты в Debian собраны тоже без нативной поддержки проверки SPF-записей. Поддержка эта имеется в Exim уже многие годы и многие годы носит статус экспериментальной. Я решил попробовать собрать пакет, в котором поддержка проверки SPF-записей включена.

Для начала скачиваем необходимое для сборки Exim:
# apt-get build-dep exim4
# apt-get source exim4
# cd exim4-4.80
Открываем файл src/EDITME в текстовом редакторе и раскомментируем строчки, включающие поддержку SPF:
EXPERIMENTAL_SPF=yes
CFLAGS  += -I/usr/local/include
LDFLAGS += -lspf2
Вызываем команду редактирования журнала изменений пакета:
# dch -i
Отмечаем изменения, которые внесли в пакет:
exim4 (4.80-7.1) UNRELEASED; urgency=low

  * Non-maintainer upload.
  * Enabled experimental SPF support.

 -- Vladimir Stupin <vladimir@stupin.su>  Sat, 12 Apr 2014 19:45:04 +0600
Собираем пакет:
# dpkg-buildpackage -us -uc -b -rfakeroot
Создаём файл /etc/apt/preferences.d/exim4 в текстовом редакторе и вносим настройки, фиксирующие пакет в системе:
Package: exim4-daemon-heavy
Pin: version 4.80-7.1
Pin-Priority: 1003
Зафиксировать пакет нужно для того, чтобы пакет из дистрибутива не заменил собранный нами вручную. Поскольку дистрибутивный пакет собран без поддержки SPF, он не сможет понять правило проверки SPF в файле конфигурации и не запустится. В результате почтовая система не будет работать. Если в дистрибутиве появится обновление пакета, пакет придётся пересобрать и установить самостоятельно.

Теперь установим пакет с поддержкой SPF:
# cd ..
# dpkg -i exim4-daemon-heavy_4.80-7.1_amd64.deb
SPF-записи могут классифицировать IP-адрес одним из следующих образов:
  • pass (+) - рекомендуется принять почту,
  • fail (-) - рекомендуется отклонить почту,
  • softfail (~) - рекомендуется принять почту, но пометить её как подозрительную,
  • neutral (?) - рекомендуется обрабатывать почту таким образом, как будто SPF-записи не существует.
Дополнительно, есть ещё два статуса, которые сообщают о постоянной или временной ошибке проверки IP-адреса.

Когда SPF-записи были только придуманы, некоторые системные администраторы слишком буквально воспринимали их рекомендации. Случались ситуации, когда первичный почтовый сервер не принимал почту от своего резервного сервера лишь потому, что его IP-адресу соответствовала SPF-запись, предписывающая не принимать письмо. Поэтому сложилась практика не указывать политику fail, а использовать вместо неё политики softfail или neutral. На мой взгляд, если бы не было таких прямолинейных системных администраторов, не было бы никакого смысла в политиках, отличных от pass и fail.

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

Перед правилами проверки квот почтовых ящиков в списке управления доступом acl_check_rcpt в секции acl файла /etc/exim4/exim4.conf добавим следующее правило, соответствующее описанным выше соображениям:
deny message = Reject due SPF policy
     spf = fail : softfail : neutral
Осталось лишь перезапустить Exim, чтобы заработал демон из собранного нами пакета и вступили в силу новые настройки:
# /etc/init.d/exim4 stop
# /etc/init.d/exim4 start
10. Требование аутентификации

Чтобы запретить локальным пользователям отправлять почту без аутентификации, добавим в конфигурацию такое правило:
deny message = Local sender must be authenticated
     sender_domains = +local_domains
     !authenticated = *
Чтобы аутентифицированный пользователь не пытался подставить чужой адрес отправителя, добавим в конфигурацию такое правило:
deny message = Send your own mail from yourself
     condition = ${if eq{$authenticated_id}{$sender_address}{no}{yes}}
     authenticated = *
Оба правила нужно добавить в секцию acl файла /etc/exim4/exim4.conf, в правило acl_check_rcpt перед принятием почты от аутентифицированных пользователей.

Осталось перезагрузить файл конфигурации, чтобы она вступила в силу:
# /etc/init.d/exim4 reload
11. Итоговый файл конфигурации
В конечном итоге у меня получился такой файл конфигурации /etc/exim4/exim4.conf:
# Имя нашей почтовой системы
primary_hostname = mail.domain.tld

# База данных MySQL и учётные данные для работы с ней
hide mysql_servers = localhost/postfixadmin/exim/exim_password

# Список доменов нашей почтовой системы
domainlist local_domains = ${lookup mysql{SELECT domain \
                                          FROM domain \
                                          WHERE domain = '${quote_mysql:$domain}' \
                                            AND backupmx = 0 \
                                            AND active = 1}}

# Список доменов, для которых наша почтовая система является резервной
domainlist relay_domains = ${lookup mysql{SELECT domain \
                                          FROM domain \
                                          WHERE domain = '${quote_mysql:$domain}' \
                                            AND backupmx = 1 \
                                            AND active = 1}}

# Список узлов, почту от которых будем принимать без проверок
hostlist relay_from_hosts = 

# Домены, для которых требуется наличие правильной DKIM-подписи
domainlist dkim_required_domains = gmail.com : yandex.ru : rambler.ru : \
                                   mail.ru : bk.ru : list.ru : inbox.ru

# Правила для проверок
acl_not_smtp = acl_check_not_smtp
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data
acl_smtp_dkim = acl_check_dkim

# Сокет-файл антивируса ClamAV
av_scanner = clamd:/var/run/clamav/clamd.ctl

# Сокет-файл SpamAssassin
# spamd_address =

# Отключаем IPv6, слушаем порты 25, 465 и 587
disable_ipv6
daemon_smtp_ports = 25 : 465 : 587
tls_on_connect_ports = 465

# Настройки сертификатов SSL/TLS
tls_advertise_hosts = *
tls_certificate = /etc/ssl/mail.domain.tld.public.pem
tls_privatekey = /etc/ssl/mail.domain.tld.private.pem

# Дописываем домены отправителя и получателя, если они не указаны
qualify_domain = domain.tld
qualify_recipient = domain.tld

# Exim никогда не должен запускать процессы от имени пользователя root
never_users = root

# Проверять прямую и обратную записи узла отправителя по DNS
host_lookup = *

# Отключаем проверку пользователей узла отправителя по протоколу ident
rfc1413_hosts = *
rfc1413_query_timeout = 0s

# Только эти узлы могут не указывать домен отправителя или получателя
sender_unqualified_hosts = +relay_from_hosts
recipient_unqualified_hosts = +relay_from_hosts

# Лимит размера сообщения, 30 мегабайт
message_size_limit = 30M

# Запрещаем использовать знак % для явной маршрутизации почты
percent_hack_domains =

# Настройки обработки ошибок доставки, используются значения по умолчанию
ignore_bounce_errors_after = 2d
timeout_frozen_after = 7d

begin acl

  # Проверки для локальных отправителей
  acl_check_not_smtp:
     accept

  # Проверки на этапе RCPT
  acl_check_rcpt:

    accept hosts = :

    # Отклоняем неправильные адреса почтовых ящиков
    deny message = Restricted characters in address
         domains = +local_domains
         local_parts = ^[.] : ^.*[@%!/|]

    # Отклоняем неправильные адреса почтовых ящиков
    deny message = Restricted characters in address
         domains = !+local_domains
         local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./

    # В локальные ящики postmaster и abuse принимает почту всегда
    accept local_parts = postmaster : abuse
           domains = +local_domains

    # Проверяем существование домена отправителя
    require verify = sender

    # Принимаем почту от доверенных узлов, попутно исправляя заголовки письма
    accept hosts = +relay_from_hosts
           control = submission

    # Не даём локальным отправителям слать почту без аутентификации
    deny message = Local sender must be authenticated
         sender_domains = +local_domains
         !authenticated = *

    # Не даём локальным отправителям представляться чужим именем
    deny message = Send your own mail from yourself
         condition = ${if eq{$authenticated_id}{$sender_address}{no}{yes}}
         authenticated = *

    # Принимаем почту от аутентифицированных узлов, попутно исправляя заголовки письма
    accept authenticated = *
           control = submission/domain=

    # Для не доверенных и не аутентифицированных требуется, чтобы получатель был в домене,
    # ящик которого находится у нас или для которого мы являемся резервным почтовым сервером
    require message = Relay not permitted
            domains = +local_domains : +relay_domains

    # Проверяем домена удалённого получателя или адрес локального получателя
    require verify = recipient

    # Отклоняем письма, не соответствующие политике домена отправителя
    deny message = Reject due SPF policy
         spf = fail : softfail : neutral

    # Проверка квот получателя
    discard message = 422 Mailbox $local_part@$domain is over quota
            domains = +local_domains
            condition = ${lookup mysql{SELECT 1 \
                                       FROM mailbox \
                                       JOIN quota2 ON quota2.username = mailbox.username \
                                         AND quota2.bytes + ${if ={$message_size}{-1}{${expand:message_size_limit}}{$message_size}} >l;= mailbox.quota \
                                       WHERE mailbox.username = LCASE('${quote_mysql:$local_part@$domain}') \
                                         AND mailbox.active = 1}}

    # Проверка квот в случае использования альтернативного домена ящика получателя
    discard message = 422 Mailbox $local_part@$domain is over quota
            domains = +local_domains
            condition = ${lookup mysql{SELECT 1 \
                                       FROM alias_domain \
                                       JOIN mailbox ON mailbox.local_part = LCASE('${quote_mysql:$local_part}') \
                                         AND mailbox.domain = alias_domain.target_domain \
                                         AND mailbox.active = 1 \
                                       JOIN quota2 ON quota2.username = mailbox.username \
                                         AND quota2.bytes + ${if ={$message_size}{-1}{${expand:message_size_limit}}{$message_size}} >= mailbox.quota \
                                       WHERE alias_domain.alias_domain = LCASE('${quote_mysql:$domain}') \
                                         AND alias_domain.active = 1}}

    # Если отправитель попал в чёрный список, отправляем его в грейлистинг
    defer message = Greylisting in action, try later
          log_message = greylisted.
          !senders = :
          !hosts = ${if exists{/etc/greylistd/whitelist-hosts}\
                              {/etc/greylistd/whitelist-hosts}{}} : \
                   ${if exists{/var/lib/greylistd/whitelist-hosts}\
                              {/var/lib/greylistd/whitelist-hosts}{}}
          dnslists = zen.spamhaus.org
          condition = ${readsocket{/var/run/greylistd/socket}\
                                  {--grey $sender_host_address $sender_address $local_part@$domain}\
                                  {5s}{}{false}}

    accept

  acl_check_data:

    # Отклоняем письма, содержащие вирусы
    deny message = Message contains a virus ($malware_name)
         malware = *

    accept

  acl_check_dkim:

    # Отклоняем письма, содержащие DKIM-подпись, если она не правильная
    deny message = Wrong DKIM signature
         dkim_status = fail

    # Отклоняем письма, не содержащие DKIM-подпись, предотвращая подделку писем
    # из крупных почтовых систем, которые всегда добавляют DKIM-подпись
    deny message = Valid DKIM signature needed for mail from $sender_domain
         sender_domains = +dkim_required_domains
         dkim_status = none

    accept

begin routers

  # Поиск транспорта для удалённых получателей
  dnslookup:
    driver = dnslookup
    domains = ! +local_domains
    transport = remote_smtp
    ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
    no_more

  # Пересылки для локальных получателей из файла /etc/aliases
  system_aliases:
    driver = redirect
    allow_fail
    allow_defer
    domains = domain.tld
    data = ${lookup{$local_part}lsearch{/etc/aliases}}

  # Пересылки с ящика на ящик в локальных доменах из Postfixadmin
  aliases:
    driver = redirect
    allow_fail
    allow_defer
    data = ${lookup mysql{SELECT LCASE(goto) \
                          FROM alias \
                          WHERE address = LCASE('${quote_mysql:$local_part@$domain}') \
                            AND active = 1}}
  
  # Пересылки на одноимённые ящики в другом домене из Postfixadmin
  alias_domain:
    driver = redirect
    allow_fail
    allow_defer
    data = ${lookup mysql{SELECT alias.goto \
                          FROM alias_domain \
                          JOIN alias ON alias.address = LCASE('${quote_mysql:$local_part@$domain}') \
                            AND alias.active = 1 \
                          WHERE alias_domain.alias_domain = LCASE('${quote_mysql:$domain}') \
                            AND alias_domain.active = 1}}

  # Пересылки на ящик по умолчанию в локальном домене из Postfixadmin
  alias_domain_catchall:
    driver = redirect
    allow_fail
    allow_defer
    data = ${lookup mysql{SELECT alias.goto \
                          FROM alias_domain \
                          JOIN alias ON alias.address = LCASE('${quote_mysql:@$domain}') \
                            AND alias.active = 1 \
                          WHERE alias_domain.alias_domain = LCASE('${quote_mysql:$domain}') \
                            AND alias_domain.active = 1}}

  # Получение почты на локальный ящик из Postfixadmin
  mailbox:
    driver = accept
    condition = ${lookup mysql{SELECT maildir \
                               FROM mailbox \
                               WHERE username = LCASE('${quote_mysql:$local_part@$domain}') \
                                 AND active = 1}{yes}{no}}
    transport = dovecot_virtual_delivery

  # Получение почты на локальный ящик с альтернативным доменным именем из Postfixadmin
  alias_domain_mailbox:
    driver = accept
    condition = ${lookup mysql{SELECT mailbox.maildir \
                               FROM alias_domain \
                               JOIN mailbox ON mailbox.local_part = LCASE('${quote_mysql:$local_part}') \
                                 AND mailbox.domain = alias_domain.target_domain \
                                 AND mailbox.active = 1 \
                               WHERE alias_domain.alias_domain = LCASE('${quote_mysql:$domain}') \
                                 AND alias_domain.active = 1}{yes}{no}}
    transport = dovecot_virtual_delivery
    cannot_route_message = Unknown user

begin transports

  # Транспорт для удалённых получателей
  # Добавляем к исходящим письмам DKIM-подпись
  remote_smtp:
    driver = smtp
    dkim_domain = ${lc:${domain:$h_from:}}
    dkim_selector = mail
    dkim_private_key = ${if exists{/etc/exim4/dkim/$dkim_selector.$dkim_domain.private} \
                                  {/etc/exim4/dkim/$dkim_selector.$dkim_domain.private}{}}

  # Транспорт для локальных получателей из Dovecot
  dovecot_virtual_delivery:
    driver = pipe
    command = /usr/lib/dovecot/dovecot-lda -d $local_part@$domain -f $sender_address
    message_prefix =
    message_suffix =
    delivery_date_add
    envelope_to_add
    return_path_add
    log_output
    user = vmail
    temp_errors = 64 : 69 : 70: 71 : 72 : 73 : 74 : 75 : 78

begin retry

  *   *   F,2h,15m; G,16h,1h,1.5; F,4d,6h

begin rewrite

begin authenticators

  # Использование LOGIN-аутентификации из Dovecot
  dovecot_login:
    driver = dovecot
    public_name = LOGIN
    server_socket = /var/run/dovecot/auth-client
    server_set_id = $auth1

  # Использование PLAIN-аутентификации из Dovecot
  dovecot_plain:
    driver = dovecot
    public_name = PLAIN
    server_socket = /var/run/dovecot/auth-client
    server_set_id = $auth1
Преимущества получившейся системы, на мой взгляд, заключаются в том, что используется небольшое количество дополнительных демонов, а всю логику работы можно понять, изучив всего один файл конфигурации. Если говорить о подсистеме SMTP, то в её состав входят лишь Exim, ClamAV и greylistd. Я намеренно не стал использовать систему оценки подозрительности письма по балльной системе (см. например Настройка почтовой системы. Агент пересылки почты Exim), чтобы в случае проблем можно было легко понять, почему определённое письмо не проходит. В случае с балльной системой понять точную причину сложнее, т.к. критическая сумма баллов может складываться из большого сочетания разных признаков.

При использовании Postfix в подсистему SMTP входили: Postfix, ClamAV, ClamAV-Milter, Milter-Greylist, OpenDKIM. Каждый демон имеет свой синтаксис конфигурационного файла, даже настройки Postfix разбросаны по десятку разных файлов. Логика работы Postfix в конечном счёте оказывается не столь очевидной, поскольку Milter-проверки вмешиваются в проверки собственно Postfix.

Если у вас есть замечания и предложения по описанным настройкам Exim, прошу высказываться, т.к. возможно я что-то упустил.

Дополнение от 3 мая 2014 года:
  1. Добавил правило проверки acl_not_smtp = acl_check_not_smtp для локальных отправителей, отправляющих почту не по протоколу SMTP, как правило, при помощи команды sendmail.
  2. Убрал лишние транспорты из маршрута system_aliases - эти транспорты в рассматриваемой конфигурации не настраивались.
  3. Уточнил маршрут system_aliases. Если система обслуживает несколько доменов, то файл /etc/aliases должен действовать только для домена, в котором находтся доменное имя почтовой системы.

воскресенье, 30 марта 2014 г.

Настройка MiniDLNA для цифрового мультимедийного плеера WD TV Play

Не столь давно я решил отказаться от видеокарты с разъёмом S-Video, к которому у меня дома был подключен телевизор. Новых видеокарт с таким разъёмом сейчас не найти, везде только VGA, DVI и HDMI. Конвертеры из HDMI и DVI в композитный, компонентный сигнал или в S-Video стоят неоправданно больших денег, причём найти их в продаже - задача непростая. Покупать вместе с новой видеокартой ещё и новый телевизор с разъёмом HDMI мне не хотелось.

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

Единственным оставшимся решением, которое позволило бы смотреть фильмы с компьютера на старом телевизоре, были цифровые мультимедийные проигрыватели. Это небольшие устройства с жёстким диском или без него, умеющее воспроизводить видео, музыку с SATA-дисков, флеш-накопителей, карт памяти или из сети Ethernet или WiFi.

После сравнения доступных предложений мой выбор пал на устройство WD TV Play. Это устройство не имеет жёсткого диска или места под его установку, более того - у него даже нет интерфейса SATA, но зато у него есть всё нужное мне: композитный видеовыход, выход для аналогового стерео и сеть Ethernet. Кроме того, имеется поддержка WiFi и разъёмы, которые могут пригодиться в дальнейшем: USB, HDMI, оптический SPDIF.

Подробную инструкцию по настройке самого устройства можно найти на его странице. Для лентяев приведу прямую ссылку на руководство: Руководство пользователя.

Внешний видПульт

1. Как это работает

Итак, в целом автоконфигурирование сети на домашних устройствах работает примерно следующим образом.

Если устройству не назначен сетевой адрес вручную, то оно сначала пытается получить настройки у DHCP-сервера. Если DHCP-сервер не отвечает, устройство выбирает случайный адрес из диапазона 169.254.1.0-169.254.255.255 и проверяет, что он свободен, выполняя ARP-запрос. Если адрес занят, устройство выбирает другой случайный IP-адрес и повторяет процедуру до тех пор, пока не обнаружит свободный IP-адрес. С выбранным адресом используется маска подсети 255.255.0.0.

После того, как сеть настроена, устройство отправляет мультикаст-запрос на адрес 239.255.255.250, на UDP-порт 1900, пытаясь обнаружить по протоколу SSDP устройства в локальной сети. Каждое устройство, предоставляющее сервис, должно ответить на запрос, сообщив в ответе HTTP-ссылку на страницу управления устройством.

Протокол SSDP позволяет обнаруживать маршрутизаторы, поддерживающие протокол IGD и мультимедийные устройства, поддерживающие протокол DLNA. Таким образом устройство может получить доступ к сети интернет, включая возможность принимать соединения из внешней сети, а также воспроизводить мультимедийные файлы, имеющиеся на устройствах домашней сети.

2. Как будем настраивать

Я не буду использовать автоконфигурирование и ручную настройку сети, а воспользуюсь более привычным для системного администратора способом раздачи настроек - при помощи DHCP-сервера ISC DHCP Server. Если у вас дома есть маршрутизатор, раздающий настройки по DHCP, или использующий самоназначенные адреса, то логично воспользоваться вместо DHCP-сервера DHCP-клиентом с поддержкой самоназначения адресов (APIPA), например из пакета dhcpcd5.

Поскольку мой мультимедийный проигрыватель работает с сетью интернет лишь как клиент, не пытаясь открыть на прослушивание порты на внешнем адресе, то мне будет достаточно настроить фаерволл вручную. Вы же можете воспользоваться протоколом IGD и настроить сервер из пакета linux-igd. Или, если вы пользуетесь домашним маршрутизатором, можно воспользоваться поддержкой IGD, имеющейся на маршрутизаторе.

Наконец, я буду использовать лишь один DLNA-сервер, поэтому он самостоятельно будет отвечать на запросы по протоколу SSDP. Если планируется настроить несколько DLNA-серверов или интегрировать их с IGD-сервером, то скорее всего вам потребуется отдельный сервер протокола SSDP, например из пакета minissdp. Теоретически, остальные приложения можно настроить так, что они будут работать через minissdp как через посредника. На практике я этого не проверял.

3. Настройка сетевого интерфейса

Моя домашняя компьютерная сеть подключена к отдельной сетевой карте компьютера. Я использую сеть 169.254.254.0/24, которая входит в диапазон IP-адресов link-local 169.254.1.0-169.254.254.255, который описан в RFC 3927. Этот диапазон используется для автоматической настройки сети на устройствах, которые не смогли найти DHCP-сервер. Устройство выбирает случайный адрес из этого диапазона и делает ARP-запрос к нему. Если на ARP-запрос никто не отвечает, устройство присваивает этот адрес себе. Если адрес занят, то процедура поиска свободного адреса повторяется.

Этот диапазон я выбрал по одной простой причине - он не используется моим провайдером, так что моя локальная домашняя сеть не пересекается с локальной сетью провайдера и устройства в моей сети могут обращаться через NAT к локальной сети провайдера.

Настройки интерфейс eth1 описываются в файле /etc/network/interfaces и имеют следующий вид:
auto eht1
iface eth1 inet static
  address 169.254.254.1
  netmask 255.255.255.0
Всё просто. Чтобы настройки вступили в силу, можно воспользоваться следующими командами:
# ifdown eth1
# ifup eth1
4. Настройка DHCP-сервера

Устройство я включил в домашнюю локальную сеть, которая подключена к отдельной сетевой карте компьютера. Если у вас есть домашний маршрутизатор, можете воткнуть устройство в него - в таком случае можно воспользоваться DHCP-сервером, встроенным в сам маршрутизатор. Можно также настроить статические IP-адреса или попытать счастья с автоматической настройкой сетевого интерфейса без участия DHCP-сервера, например, при помощи демона avahi-autoipd.

Чтобы устройство могло получить настройки сети от компьютера автоматически, я настроила на своём компьютере DHCP-сервер. Мой выбор автоматически остановился на привычном мне ISC DHCP Server, что кому-то может показаться забиванием гвоздей микроскопом. Вы можете настроить другой DHCP-сервер.

Итак, установим пакет с DHCP-сервером:
# apt-get install isc-dhcp-server
Создадим файл конфигурации /etc/dhcp/dhcpd.conf со следующим содержимым:
# Не нужно пытаться обновлять данные на DNS-сервере
ddns-update-style none;

# Этот DHCP-сервер в сети - главный
authoritative;

# "Средство" syslog, которое DHCP-сервер будет использовать для отправки
# диагностических сообщений в syslog
log-facility local7;

subnet 169.254.254.0 netmask 255.255.255.0 {
  # Диапазон адресов, выдаваемый в аренду
  range 169.254.254.2 169.254.254.254;

  # Суффикс, используемый для решения запросов с неполным доменным именем
  option domain-name "stupin.su";

  # Локальный кэширующий DNS-сервер (настройка не описана)
  option domain-name-servers 169.254.254.1;

  # Клиент должен продлевать аренду по прошествии суток
  default-lease-time 86400;

  # Аренда считается истекшей по истечение двух суток
  max-lease-time 172800;

  # Локальный NTP-сервер (настройка не описана)
  option ntp-servers 169.254.254.1;
  
  # Маршрут по умолчанию
  option routers 169.254.254.1;
  
  # Широковещательный адрес сети
  option broadcast-address 169.254.254.255;
}

host wd {
  hardware ethernet 00:90:a9:62:b2:99;
  fixed-address 169.254.254.2;
}
В файл /etc/default/isc-dhcp-server впишем имя интерфейса, на котором DHCP-сервер должен принимать запросы:
INTERFACES="eth1"
Осталось перезапустить DHCP-сервер, чтобы его новые настройки вступили в силу:
# /etc/init.d/isc-dhcp-server restart
На самом деле устройство способно работать только с первым DNS-сервером, а NTP-серверы он попросту игнорирует, предпочитая всегда пользоваться серверами pool.ntp.org.

Мой плеер имеет MAC-адрес 00:90:a9:62:b2:99 - его я узнал при помощи tcpdump. Этому MAC-адресу всегда будет выдаваться один и тот же IP-адрес 169.254.254.2.

5. Настройка фаерволла

До сих пор у меня ещё не было повода расписывать, как я обычно настраиваю фаерволл, поскольку обычно мне приходится настраивать прохождение пакетов для классических интернет-протоколов, вроде HTTP, FTP, SMTP, POP3, IMAPv4, DNS, NTP. Там всё довольно просто - достаточно открыть TCP или UDP-порт в нужном направлении, а в случае FTP ещё загрузить несколько модулей ядра и прописать их в автозагрузку.

В этом случае речь идёт о протоколе под названием SSDP, который не назовёшь классическим. Поэтому я решил описать настроенные правила в этой заметке. Однако, для начала я опишу минимальную настройку фаерволла для рабочей станции, на которой не запущен ни один сетевой сервис. Обычно я ограничиваюсь всего тремя правилами и политикой по умолчанию для входящих пакетов DROP:
# iptables -A INPUT -s 127.0.0.1/32 -i lo -j ACCEPT
# iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# iptables -A INPUT -p icmp -m icmp ! --icmp-type 5 -j ACCEPT
# iptables -P INPUT DROP
При такой конфигурации наш компьютер может инициировать обмен с кем угодно, получать от него ответы, но сам будет отвечать только на ICMP-запросы.

Для того, чтобы эти настройки восстанавливались при каждой загрузке, я сохраняю их при помощи команды iptables-save:
# iptables-save > /etc/network/iptables
И прописываю загрузку правил при настройке интерфейса lo в файле /etc/network/interfaces:
auto lo
iface lo inet loopback
  pre-up iptables-restore < /etc/network/iptables
Теперь перейдём к настройке доступа со стороны мультимедиа-центра к серверу MiniDLNA на нашем сервере:
# iptables -A INPUT -i eth1 -s 169.254.254.2 -d 169.254.254.1 -p tcp -m tcp --dport 8200 -j ACCEPT
# iptables -A INPUT -i eth1 -s 169.254.254.2 -d 169.254.254.1 -p udp -m udp --dport 1900 -j ACCEPT
# iptables -A INPUT -i eth1 -s 169.254.254.2 -d 239.255.255.250 -p udp -m udp --dport 1900 -j ACCEPT
UDP-порт 1900 - это порт протокола SSDP, позволяющего устройствам сети обнаруживать друг друга и обмениваться информацией. Для обнаружения используется мультикаст-адрес 239.255.255.250.

На TCP-порту 8200 работает встроенный в MiniDLNA веб-сервер. На нём есть всего одна страница, на которой можно увидеть количество доступных на сервере видео-, аудио- и графических файлов. В случае более сложных DLNA серверов там может быть, например, веб-интерфейс для управления устройством и т.п. Устройства узнают о веб-серверах друг друга по протоколу SSDP.

Кроме того, устройство обращается в сеть интернет для получения данных о времени, о погоде в выбранном городе и при использовании онлайн-сервисов. Настроим правила фаерволла для транзитных пакетов:
# iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# iptables -P FORWARD DROP
Эти правила разрешают проход пакетов, относящихся к уже открытым установленным соединениям. Всем остальным пакетам по умолчанию ход запрещён - такие пакеты будут просто отбрасываться. Любые разрешения должны быть прописаны в фаерволл явным образом.

Чтобы плеер не ждал истечения таймаута при обращении к серверам производителя при попытке проверить наличие обновлений, я запрещу плееру обращаться к веб-серверам так, чтобы в ответ на попытки установить подключение к веб-серверу, плееру приходил ICMP-пакет, сообщающий о том, что соответствующий порт сервера закрыт:
# iptables -A FORWARD -i eth1 -s 169.254.254.2 -p tcp -m multiport --dports 80,443 -j REJECT --reject-with icmp-port-unreachable
Закрываю доступ по одной простой причине - этим плеером можно управлять через интернет, с веб-сайта производителя, что я считаю огромной дырой в системе безопасности.

Так как плеер не умеет использовать NTP-сервер, выданный DHCP-сервером, а всегда обращается к серверам pool.ntp.org, добавим в правила фаерволла принудительное перенаправление всех пакетов, идущих со стороны плеера на UDP-порт 123, на UDP-порт 123 моего NTP-сервера и разрешим пакетам ходить в этом направлении:
# iptables -A PREROUTING -i eth1 -s 169.254.254.2 -p udp -m udp --dport 123 -j DNAT --to-destination 169.254.254.1:123
# iptables -A FORWARD -i eth1 -s 169.254.254.2 -d 169.254.254.1 -p udp -m udp --dport 123 -j ACCEPT
Поскольку моя домашняя сеть не будет маршрутизироваться в сети провайдера, я выполняю трансляцию исходного адреса на внешних, провайдерских интерфейсах:
# iptables -A POSTROUTING -o ppp0 -j MASQUERADE
# iptables -A POSTROUTING -o eth0 -j MASQUERADE
Ответные пакеты будут транслироваться в обратном направлении автоматически. Осталось запомнить новые настройки фаерволла в файле /etc/network/iptables и включить маршрутизацию:
# iptables-save > /etc/network/iptables
# sysctl -w net.ipv4.conf.all.forwarding=1
Чтобы маршрутизация автоматически включалась при перезагрузке, пропишем в файл /etc/sysctl.conf ту же самую настройку:
net.ipv4.conf.all.forwarding = 1
6. Настройка MiniDLNA

Остался финальный штрих - настройка самого сервера MiniDLNA. Для этого нужно прописать в файл /etc/minidlna.conf следующие настройки:
# Прописываем каталоги с видео-, аудио и графическими файлами
# Можно указывать произвольное количество каталогов, можно не указывать тип источника
media_dir=V,/home/video
media_dir=A,/home/music
media_dir=P,/home/photos

# Слушать будем указанный интерфейс и IP-адрес
network_interface=eth1
listening_ip=169.254.254.1

# TCP-порт встроенного веб-сервера
port=8200

# Имя хранилища, под которым его будет видно на других устройствах
friendly_name=stupin.su minidlna

# Серийный номер нашего хранилища, модель и номер модели
serial=12345678
model_name=Windows Media Connect compatible (MiniDLNA)
model_number=1

# Использовать механизм inotify:
# ядро Linux будет оповещать MiniDLNA об изменении интересующих файлов
inotify=yes

# В качестве обложек музыкальных альбомов использовать файлы с указанными именами
album_art_names=Cover.jpg/cover.jpg/Album.jpg/album.jpg/Folder.jpg/folder.jpg/Thumb.jpg/thumb.jpg
По умолчанию MiniDLNA сам открывает UDP-порт 1900 протокола SSDP. Но, если вы собрались запустить на компьютере несколько программ, которые используют протокол SSDP, можно настроить отдельный SSDP-сервер MiniSSDP. Сокет-файл этого сервера можно в дальнейшем указывать всем программам, которые пользуются протоколом SSDP. В случае MiniDLNA путь к сокет-файлу указывается в конфигурационном файле /etc/minidlna.conf, в настройке minissdpdsocket.

Осталось убедиться, что в файле /etc/default/minidlna разрешён запуск сервера:
START_DAEMON="yes"
И можно запустить его:
# /etc/init.d/minidlna restart
Задняя панельТелевизор

7. Ссылки

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

Ниже приведён небольшой список ссылок на краткое описание различных протоколов, имеющих отношение к автоматической настройке локальной сети:
  • Zeroconf - описание самой идеи собирательного названия Zeroconf, диапазона локальных адресов для самонастройки IPv4LL - IPv4 Link-Local, способа самонастройки локальных адресов APIPA - Automatic Private IP Addressing
  • RFC 3927 - формальное описание диапазона локальных адресов для самонастройки IPv4LL - IPv4 Link-Local
  • UPnP - описание группы протоколов Universal Plug and Play
  • SSDP - описание протокола обнаружения сервисов Simple Service Discovery Protocol, входящего в группу протоколов UPnP
  • DLNA - описание протокола Digital Living Network Alliance для обмена мультимедийными данными, входящего в группу протоколов UPnP
  • IGD - описание протокола настройки шлюза Internet Gateway Device Protocol
  • NAT-PMP - другой протокол управления шлюзом NAT Port Mapping Protocol
  • PCP - ещё один протокол управления шлюзом Port Control Protocol
P.S. 8 апреля 2018 года внесён ряд правок в настройки фаерволла: веб-трафик запрещён с ICMP-сообщением о закрытом порте, NTP-трафик перенаправляется на локальный NTP-сервер.

воскресенье, 23 марта 2014 г.

Установка и настройка TinyTinyRSS

TinyTinyRSS - это веб-приложение для чтения RSS-подписок. Оно использует MySQL для хранения учётных записей пользователей и информации из лент новостей. Для обновления лент используется отдельный демон или скрипт, запускаемый при помощи планировщика задач.

1. Скачивание и распаковка

Находим на странице http://tt-rss.org/redmine/projects/tt-rss/wiki ссылку на скачивание. Скачиваем тарболл и распаковываем:
$ wget https://github.com/gothfox/Tiny-Tiny-RSS/archive/1.11.tar.gz
$ tar xzvf Tiny-Tiny-RSS-1.11.tar.gz
Перемещаем каталог с приложением туда, откуда к нему будет обращаться веб-сервер:
# mv Tiny-Tiny-RSS-1.11 /usr/local/share/ttrss
И даём веб-серверу права доступа к каталогу с приложением:
# chown www-data:www-data /usr/local/share/ttrss
# find /usr/local/share/ttrss -type d -exec chmod u=rwx,g=rx,o= \{\} \;
# find /usr/local/share/ttrss -type f -exec chmod u=rw,g=r,o= \{\} \;
2. Настройка пула в php5-fpm

Если php5-fpm ещё не был установлен, время сделать это:
# apt-get install php5-fpm
По умолчанию в php5-fpm имеется общий пул, настройки которого находятся в файле /etc/php/fpm/pool.d/www.conf, но в данном случае я предпочту настроить для этого приложения отдельный пул, поскольку это приложение будет использоваться редко и я не хочу постоянно держать запущенными лишние процессы. Кроме того, разные пулы, даже будучи запущенными от имени одного и того же пользователя, позволяют снизить эффект взаимного влияния разных приложений друг на друга: если одно из приложений вдруг начнёт использоваться больше остальных, оно не займёт процессы других пулов, выделенные для обработки других приложений.

Создадим файл пула /etc/php5/fpm/pool.d/rss.conf со следующим содержимым:
[rss]
user = www-data
group = www-data
listen = /var/run/rss.sock

listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = ondemand
pm.max_children = 2
pm.process_idle_timeout = 10m

access.log = /var/log/rss.access.log
Пул называется rss, процессы пула работают от имени пользователя www-data и группы www-data. Пул обрабатывают запросы, поступающие на юникс-сокет /var/run/rss.sock, доступный на чтение и запись только пользователю и группе www-data. Процессы пула порождаются только при обращении и завершаются, если к ним не было обращений более 10 минут. В пуле может быть не более 2 процессов. Журнал обработанных запросов будет вестись в файле /var/log/rss.access.log

Осталось перезагрузить php5-fpm, чтобы добавить новый пул:
# /etc/init.d/php5-fpm reload
3. Настройка веб-сервера nginx

Если в системе ещё не установлен веб-сервер nginx, поставим его:
# apt-get install nginx-light
Теперь можно отредактировать файл /etc/nginx/sites-available/default, добавив в него следующую секцию:
location /rss {
  alias /usr/local/share/ttrss;

  location ~ \.php$ {
    fastcgi_pass unix:/var/run/rss.sock;
    fastcgi_index index.php;

    include fastcgi_params;
  }
}
Если нужно настроить TinyTinyRSS для работы на отдельном доменном имени, например rss.domain.tld, то можно создать файл /etc/nginx/sites-available/rss со следующим содержимым:
server {
  listen 80;

  server_name rss.domain.tld;

  root /usr/local/share/ttrss;

  location ~ \.php$ {
    fastcgi_pass unix:/var/run/rss.sock;
    fastcgi_index index.php;

    include fastcgi_params;
  }
}
Чтобы вновь созданная конфигурация была задействована, нужно создать символическую ссылку на неё в каталоге /etc/nginx/sites-enabled/:
# cd /etc/nginx/sites-enabled/
# ln -s /etc/nginx/sites-available/rss rss
Осталось перезапустить веб-сервер:
# /etc/init.d/nginx restart
4. Подготовка базы данных

Разработчики TinyTinyRSS рекомендуют для хранения настроек и лент использовать СУБД PostgreSQL, но возможно использовать и MySQL. Именно его я и настрою. Если сервер MySQL ещё не установлен, сделайте это:
# apt-get install mysql-server-5.5
После установки СУБД, нужно подготовить пустую базу данных и пользователя, от имени которого приложение будет подключаться к базе данных. Соответственно, этот пользователь должен иметь полный доступ к своей базе данных. Создадим базу данных и пользователя:
$ mysql -uroot -p mysql
> CREATE DATABASE rss CHARSET UTF8;
> INSERT INTO user(user, password, host) VALUES('rss', PASSWORD('rss_password'), 'localhost');
> FLUSH PRIVILEGES;
> GRANT ALL ON rss.* TO rss@localhost;
> FLUSH PRIVILEGES;
Вместо пароля rss_password я подставил 16-символьный пароль, сгенерированный программой pwgen из одноимённого пакета:
$ pwgen 16
5. Веб-настройка приложения

Теперь перейдём на страницу настройки веб-сервера http://domain.tld/rss/install/ или http://rss.domain.tld/install/, в зависимости от выбранного вами варианта настройки веб-сервера:

Вводим настройки подключения к базе данных и нажимаем кнопку "Test configuration":
Ввод настроек подключения к базе данных

После успешной проверки подключения к базе данных нажимаем на кнопку "Initialize database":
Инициализация базы данных

Запоминаем имя созданного пользователя admin и его пароль password, а затем сохраняем сгенерированный файл конфигурации нажатием на кнопку "Save configuration":
Сохранение конфигурации

После того, как конфигурация сохранилась, можно перейти по ссылке с текстом "loading tt-rss now":
Переход по ссылке

Страница входа в приложение:
Вход в приложение

Не забудьте сразу же войти в систему и поменять пароль пользователя admin на нечто более сложное, нежели слово password.

6. Настройка демона обновлений

На странице http://tt-rss.org/redmine/projects/tt-rss/wiki/UpdatingFeeds описано несколько разных способов обновления RSS-лент. Я решил остановиться на многопроцессном демоне обновлений update_daemon2.php. Добавим демону права на запуск:
# chmod ug+x /usr/local/share/ttrss/update_daemon2.php
Теперь создадим init-файл /etc/init.d/ttrss для управления демоном (его можно скачать по ссылке http://stupin.su/files/ttrss):
#!/bin/sh
### BEGIN INIT INFO
# Provides:          ttrss
# Required-Start:    $syslog $remote_fs $network
# Required-Stop:     $syslog $remote_fs $network
# Should-Start:
# Should-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start the TinyTinyRSS updates daemon.
# Description:       Start the TinyTinyRSS updates daemon.
### END INIT INFO

PATH=/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/local/share/ttrss/update_daemon2.php
NAME=ttrss
DESC="TinyTinyRSS update daemon"
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
USER=www-data
GROUP=www-data

test -x $DAEMON || exit 0

set -e

. /lib/lsb/init-functions

case "$1" in
    start)
        log_daemon_msg "Starting $DESC" $NAME
        if ! start-stop-daemon --start --oknodo --quiet --background --make-pidfile \
            --chuid $USER:$GROUP --pidfile $PIDFILE --exec $DAEMON
        then
            log_end_msg 1
        else
            log_end_msg 0
        fi
        ;;
    stop)
        log_daemon_msg "Stopping $DESC" $NAME
        if start-stop-daemon --stop --retry 30 --oknodo --quiet \
            --pidfile $PIDFILE --exec $DAEMON
        then
            rm -f $PIDFILE
            log_end_msg 0
        else
            log_end_msg 1
        fi
        ;;
    restart|reload|force-reload)
        check_syntax
        $0 stop
        $0 start
        ;;
    status)
        status_of_proc -p "$PIDFILE" "$DAEMON" ttrss && exit 0 || exit $?
        ;;
    *)
        echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload|status}" >&2
        exit 1
        ;;
esac

exit 0
Включим автоматический запуск и остановку демона в процессе загрузки, выключения и перезагрузки системы:
# insserv ttrss
И запустим демон обновлений прямо сейчас:
# /etc/init.d/ttrss start
Приложение настроено, дальнейшая процедура создания пользователей и добавления подписок уже выходит за рамки заметки.