воскресенье, 2 ноября 2014 г.

NUT и Eaton Powerware 5110

Эта заметка будет необычно длинной и нудной :) Дело в том, что поначалу система NUT показалась мне сложной, поэтому я начал читать man'ы, файлы конфигурации и выписывать понятое. Как оказалось, всё очень просто, но не пропадать же добру? Вот и решил выложить всё, что накопилось.

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

1. Серверная часть

Установим на компьютер с ИБП серверную часть NUT:
# apt-get install nut-server
Настройки сервера NUT находятся в трёх файлах:
  • /etc/nut/ups.conf - настройки драйверов ИБП,
  • /etc/nut/upsd.conf - настройки сервера,
  • /etc/nut/upsd.users - настройки аутентификации и прав пользователей сервера.
1.1. Драйверы ИБП

В файле /etc/nut/ups.conf описываются ИБП, подключенные к этому компьютеру. Файл представляет собой INI-файл, в котором секцией является имя ИБП, а внутри секции прописываются настройки драйвера ИБП. Для начала опишу глобальные настройки:
  • chroot - не обязательный параметр, указывающий chroot-каталог, в котором будет запускаться драйвер ИБП,
  • driverpath - не обязательный параметр, указывающий путь к исполняемым файлам драйверов ИБП,
  • maxstartdelay - не обязательный параметр, значение которого можно указать локально, в секции с описанием ИБП. Указывает время, в течение которого ожидается завершение запуска драйвера ИБП. По умолчанию - 45 секунд,
  • pollinterval - не обязательный параметр, указывающий интервалы времени, через которые необходимо запрашивать у ИБП свежие данные о его состоянии.
Локальные настройки, описывающие конкретный ИБП:
  • dirver - обязательный параметр, указывающий, какую программу использовать для взаимодействия с ИБП. Найти подходящий драйвер можно в файле /usr/share/nut/driver.list,
  • port - обязательный параметр, указывающий используемый для связи с ИБП последовательный или USB-порт. Возможными значениями могут быть файлы устройств вроде /dev/ttyS0 или значение auto,
  • sdorder - если к системе подключено несколько ИБП, то этот параметр указывает очерёдность отключения этого ИБП. Чтобы не отключать ИБП, здесь можно указать -1. Значение по умолчанию - 0,
  • desc - не обязательный параметр, описывающий ИБП. Может содержать произвольный текст,
  • nolock - не обязательный флаг, указывающий не блокировать порт связи с ИБП. В этом случае другие программы смогут работать с портом, но указывать этот флаг не рекомендуется,
  • ignorelb - не обязательный флаг, предписывающий игнорировать состояние ИБП разряда батареи. Вместо этого используются данные о текущем заряде батареи и его предельно низком значении (battery.charge < battery.charge.low) или о расчётном времени работы батареи и минимально допустимом времени работы от батареи (battery.runtime < battery.runtime.low). Это позволяет вручную задать предельные значения, по достижении которых батарея ИБП будет считаться разряженной (например, override.battery.charge.low = 30 - осталось 30% заряда батареи или override.battery.runtime.low = 180 - осталось 3 минуты работы от батареи),
  • default.переменная - не обязательный параметр, который задаёт значение по умолчанию для тех переменных, данные о которых ИБП не отдаёт,
  • override.переменная - не обязательный параметр, значение которого заменяет значение, отдаваемое ИБП.
Драйвер может иметь собственные, специфичные только для него настройки и команды, о которых можно прочитать на странице руководства man выбранного драйвера. Например, у драйвера bcmxcp_usb, конфигурация которого будет приведена ниже, имеются следующие настройки и команды:
  • shutdown_delay - пауза в секундах, которую ИБП выдержит между получением команды на отключение и её выполнением. По умолчанию - 120 секунд,
  • shutdown.return - команда отключить нагрузку и снова подать питание,
  • shutdown.stayoff - команда отключить нагрузку и не включать,
  • test.battery.start - команда начать проверку батареи.
В файл /etc/nut/ups.conf пропишем настройки драйвера ИБП:
[Powerware5110]
driver = bcmxcp_usb
port = auto
desc = "UPS on computer fstu"
1.2. Сервер NUT

Теперь настроим сервер, предоставляющий информацию о состоянии ИБП клиентам:
  • MAXAGE - максимальное время, в течение которого полученные от ИБП данные считаются актуальными. По умолчанию - 15 секунд,
  • STATEPATH - каталог, в котором размещаются сокет-файлы драйверов ИБП. По умолчанию /var/run/nut,
  • MAXCONN - максимальное количество разрешённых подключений к серверу, включая драйверы, директивы LISTEN и подключенных клиентов. По умолчанию - 1024,
  • CERTFILE - если вы хотите шифровать сетевые подключения, укажите здесь путь к PEM-файлу с сертификатом и ключом сервера. По умолчанию шифрование не используется,
  • LISTEN - директива, указывающая IP-адрес и TCP-порт, на которых сервер будет ожидать запросы клиентов. Может быть указана несколько раз. Может быть указан IPv6-адрес.
Поскольку в нашем случае единственный клиент располагается на компьютере с сервером, пропишем в файл /etc/nut/upsd.conf одну директиву:
LISTEN 127.0.0.1 3493
В файле /etc/nut/upsd.users описываются пользователи, которые будут работать с сервером. Файл имеет формат INI-файла, в котором именем секции является имя пользователя, а параметры внутри секции описывают права пользователя.
  • password - параметр задаёт пароль пользователя,
  • actions - параметр описывает разрешённые пользователю действия. Может быть указан несколько раз. Всего доступно два действия: SET - возможность устанавливать значения переменных ИБП, FSD - возможность инициировать ситуацию отсутствия питания на ИБП и низкого заряда батареи, которая может инициировать завершение работы компьютеров, запитанных от ИБП,
  • instcmds - параметр, разрешающий отдавать ИБП определённые команды. Может быть указан несколько раз. Чтобы разрешить все команды, можно указать значение ALL. Чтобы узнать список доступных команд, можно воспользоваться командой "upscmd -l". Полный список всех команд и переменных, поддерживаемых системой NUT, можно посмотреть в файле /usr/share/nut/cmdvartab,
  • upsmon master - флаг описывает пользователя, от имени которого будет подключаться клиент мониторинга, работающий на том же компьютере, что и сервер (и куда подключен интерфейс взаимодействия с ИБП),
  • upsmon slave - флаг описывает пользователя, от имени которого будет подключаться клиент мониторинга, работающий на компьютере отличном от того, где запущен сервер.
Теперь настроим доступ к серверу в файле /etc/nut/upsd.users. Добавим пользователя-администратора и пользователя, от имени которого будет подключаться клиент мониторинга, запущенный на том же компьютере, что и сервер:
[admin]
password = password
actions = SET
instcmds = ALL

[upsmon]
password = password
upsmon master
Если предполагается, что к серверу будут подключаться клиенты мониторинга, работающие на других компьютерах, можно добавить ещё одного пользователя с настройкой upsmon slave.

Осталось перезапустить сервер, чтобы его настройки вступили в силу:
# /etc/init.d/nut-server restart
2. Клиентская часть

На компьютеры, запитанные от ИБП нужно поставить пакет с консольными клиентами NUT:
# apt-get install nut-client
Внутри пакета есть три консольных клиента, умеющих взаимодействовать с сервером NUT:
  • upsc - клиент, при помощи которого можно запросить текущие значения переменных на ИБП,
  • upscmd - клиент, при помощи которого можно установить значения переменных ИБП или выполнять команды,
  • upsmon - клиент, следящий за состоянием одного или нескольких ИБП, который отсылает уведомления или выполняет команду отключения компьютера. Его конфигурация находится в файле /etc/nut/upsmon.conf.
В составе клиента имеется также утилита upssched с файлом конфигурации /etc/nut/upssched.conf, которая помогает осуществлять отложенное исполнение команд. В файле конфигурации можно настроить таймер, который будет запущен при наступлении одних событий (например, когда в ИБП не поступает внешнее питание, а его батарея разрядилась) или сброшен при наступлении других событий (например, когда на ИБП вновь начало поступать внешнее питание). Когда таймер срабатывает, выполняется указанная команда.

В файле /etc/nut/nut.conf имеется единственный параметр MODE, задающий режим работы клиента upsmon. Имеется четыре режима работы системы NUT:
  • none - система выключена,
  • standalone - автономная система, в которой интерфейс управления ИБП подключен к тому же компьютеру, который запитан от ИБП. Других компьютеров, запитанных от ИБП нет,
  • netserver - система, в которой от ИБП запитано несколько компьютеров. В этом случае сервер NUT должен быть доступен по сети для остальных компьютеров,
  • netclient - система, запитанная от ИБП, интерфейс управления которым подключен к другому компьютеру.
В нашем случае подходит значение standalone, которое и пропишем в файл /etc/nut/nut.conf:
MODE=standalone
2.1. Клиент для просмотра состояния ИБП

Текущее состояние ИБП можно посмотреть при помощи следующей команды:
$ upsc Powerware5110@localhost
Текущее значение любого из параметров можно посмотреть при помощи команды с указанием имени параметра. Например, текущий статус можно посмотреть при помощи такой команды:
$ upsc Powerware5110@localhost ups.status
Возможные значения статуса:
  • OL - ИБП работает от сети,
  • OB - ИБП работает от батареи,
  • LB - батарея разряжена,
  • RB - необходима замена батареи,
  • CHRG - батарея заряжается,
  • DISCHRG - батарея разряжается,
  • BYPASS - ИБП работает в режиме питания от сети в обход батареи,
  • OVER - ИБП перегружен,
  • TRIM - ИБП отрезает повышенное напряжение сети,
  • BOOST - ИБП усиливает пониженное напряжение сети,
2.2. Клиент для записи переменных ИБП

Для выполнения команд на ИБП необходимо предоставить имя пользователя и его пароль. Например, вот так можно заставить ИБП отключить потребителей, а при появлении питания на входе снова включить их:
$ upscmd -u admin -p password Powerware5110@localhost shutdown.return
Если команда принимает дополнительные параметры (например, время в секундах), то их значения можно указать после команды ИБП, в качестве дополнительного аргумента.

2.3. Клиент мониторинга ИБП

Настроим клиента мониторинга, который наблюдает за состоянием ИБП и при необходимости выключает компьютер. Для этого впишем в файл /etc/nut/upsmon.conf следующую конфигурацию:
# Описание ИБП, за которым нужно наблюдать
MONITOR Powerware5110@localhost 1 upsmon password master

# Команда, которую нужно выполнить перед пропаданием питания от ИБП
SHUTDOWNCMD "/sbin/shutdown -h +0"
В настройке MONITOR цифра 1 означает количество блоков питания компьютера, запитанных от этого ИБП. Большинство компьютеров имеет только один блок питания. Можно указать значение 0, если компьютер не запитан от ИБП, но необходимо следить за состоянием ИБП.

Кроме указанных настроек доступны также следующие:
# Пользователь, от имени которого будет работать клиент мониторинга
# RUN_AS_USER nutmon

# Минимальное количество запитанных блоков питания компьютера, необходимых для его работы
# Например, если компьютер обладает 4 блоками питания, но для работы достаточно 2, то
# здесь нужно вписать 2. Количество блоков питания, запитанных от определённого ИБП
# указывается в настройке MONITOR
MINSUPPLIES 1

# Обычная частота опроса состояния ИБП
POLLFREQ 5

# Частота опроса состояния ИБП, если он работает от батареи
POLLFREQALERT 5

# Интервал, в течение которого команда на отключение должна быть подтверждена
# Указывается на случай, если в течение этого времени вновь появится питание
# и отключение будет отменено
HOSTSYNC 15

# Если в течение этого интервала данные от ИБП не обновлялись, ИБП помечается
# как недоступный и заряд его батареи будет считаться низким, что может
# спровоцировать отключение компьютеров, запитанных от него
DEADTIME 15
Перезапустим клиента мониторинга:
# /etc/init.d/nut-client restart
Протестировать выключение системы в случае пропадания питания и разряда батареи, можно при помощи команды:
# upsmon -c fsd
2.4. Графический клиент

Графический клиент находится в пакете nut-monitor. Установим его:
# apt-get install nut-monitor
Чтобы подключиться к ИБП, нужно указать IP-адрес или имя сервера, порт и, если нужно, имя пользователя и пароль:


После подключения можно посмотреть краткую сводку по текущему состоянию ИБП:


На отдельной вкладке можно посмотреть значение всех переменных ИБП:


Можно добавлять ИБП в закладки и переключаться между ними:


Клиента можно свернуть в трей, он примет вид батарейки:

2.5. Веб-клиент

Веб-клиент находится в пакете nut-cgi. Установим сам клиент, а также nginx и uwsgi для запуска CGI-приложений:
# apt-get install nut-cgi nginx uwsgi
Настройки веб-клиента располагаются в файлах /etc/nut/upsset.conf и /etc/nut/hosts.conf.

В первом файле нет никаких настроек. Его единственное назначение - отключить веб-интерфейс до тех пор, пока вы не ограничите в веб-сервере список узлов, с которых будет доступен этот веб-интерфейс. Когда доступ ограничен, можно раскомментировать единственную опцию из этого файла:
I_HAVE_SECURED_MY_CGI_DIRECTORY
Во втором файле находится список ИБП, состояние которых можно будет просматривать через этот веб-интерфейс. Директивы имеют следующий вид:
MONITOR myups@localhost "Local UPS"
В нашем случае в этом файле будет лишь одна директива:
MONITOR Powerware5110@localhost "Powerware5110 on my computer"
Для запуска веб-клиента NUT будем использовать uwsgi и nginx. Настроим пул процессов, обслуживающих CGI-приложение NUT в файле /etc/uwsgi/apps-available/cgi.ini:
[uwsgi]

procname = uwsgi-cgi
procname-master = uwsgi-cgi-master

plugins = cgi
cgi = /usr/lib/
cgi-allowed-ext = .cgi
cgi-timeout = 300
processes = 2
Включим использование этого приложения:
# cd /etc/uwsgi/apps-enabled/
# ln -s /etc/uwsgi/apps-available .
Запустим включенное приложение uwsgi:
# /etc/init.d/uwsgi start cgi
Теперь настроим обработку доступа к CGI-страницам в файле /etc/nginx/sites-available/default:
server {
  listen 127.0.0.1:80; 
  server_name localhost;

  root /usr/share/nut/www;
  index index.html;

  location ~ ^/cgi-bin/nut/(upsimage|upsset|upsstats)\.cgi$ {
    allow 127.0.0.1;
    deny all;
    
    include uwsgi_params;
    uwsgi_modifier1 9;
    uwsgi_pass unix:/var/run/uwsgi/app/cgi/socket;
    uwsgi_param SCRIPT_FILENAME /usr/lib/cgi-bin/nut/$1.cgi;
  }
}
Включим использование этого сайта:
# cd /etc/nginx/sites-enabled/
# ln -s /etc/nginx/sites-available .
Запустим включенное приложение uwsgi:
# /etc/init.d/nginx reload
Директивы allow и deny в файле конфигурации nginx ограничивают доступ к веб-клиенту NUT только IP-адресом самого компьютера. Если в директиве listen указан IP-адрес 127.0.0.1, то директивы deny и allow указывать не обязательно, т.к. к веб-серверу просто неоткуда больше подключиться, кроме как с самого компьютера. Если же веб-сервер прослушивает какие-то внешние IP-адреса, то в директивах allow нужно перечислить те IP-адреса или подсети, с которых необходим доступ к веб-интерфейсу.

На странице http://localhost/cgi-bin/nut/upsstats.cgi можно увидеть таблицу с ИБП с кратким описанием текущего состояния каждого из них. В моём случае ИБП всего один:


Если в таблице ИБП перейти по ссылке в левом столбце, то можно попасть на такую вот страницу с диаграммами текущего состояния ИБП:


Если в таблице ИБП перейти по ссылке в правом столбце, то можно попасть на страницу с текущими значениями переменных ИБП:


воскресенье, 5 октября 2014 г.

Настройка почты в Redmine, обновление структуры базы данных

1. Настройка почты

Для отправки уведомлений об изменении в задачах заинтересованным лицам, в каталоге с настройками Redmine можно создать файл /etc/redmine/default/configuration.yml следующего вида:
production:
  email_delivery:
    delivery_method: :smtp
    smtp_settings:
      address: mail.domain.tld
      port: 587
      domain: domain.tld
      authentication: :login
      user_name: redmine@domain.tld
      password: uxai5eikahf2Ik8H
Смысл настроек понятен из их названия. Существует ещё возможность отправлять почту при помощи локальной команды sendmail. Подробнее о настройках отправки почты можно почитать здесь: Настройка почты

Теперь нужно перезапустить redmine, чтобы он заново прочитал файлы конфигурации:
# /etc/init.d/uwsgi restart redmine
Теперь нужно зайти в Redmine, перейти в раздел "Администрирование" -> "Настройки":


Во вкладке "Общие" поправить поле "Имя компьютера", указав веб-адрес, по которому доступен Redmine, но без протокола и без косых черт слева и справа:


Во вкладке "Уведомления по email" нужно выбрать события, уведомления о которых нужно отсылать, указать адрес, с которого будут отправляться письма:

Там же можно указать заголовок письма и текст, который будет добавлен ниже тела письма (обычно там пишут про то, что на сообщение не нужно отвечать, что оно сгенерировано автоматически, почему оно пришло адресату или как отписаться от писем).

Адреса пользователей можно редактировать в их профилях.

2. Обновление структуры базы данных

Если у вас имеется база данных от старой версии Redmine, которую вы хотите развернуть на более свежей версии Redmine, то обновить структуру базы данных до более свежей версии очень просто:
# cd /usr/share/redmine
# rake db:migrate RAILS_ENV=production X_DEBIAN_SITEID=support
В Debian существует возможность легко запустить на одном компьютере несколько разных экземпляров Redmine. В переменной X_DEBIAN_SITEID можно указать имя экземпляра Redmine. Имя экземпляра задаёт имя подкаталога в каталоге /etc/redmine/. В подкаталоге экземпляра находятся файлы с настройками Redmine. Если имя экземпляра не указывать, то подразумевается экземпляр default и используются настройки из каталога /etc/redmine/default/.

В Debian даже есть возможность управлять созданием и удалением экземпляров Redmine при помощи мастера, которого можно запустить по команде dpkg-reconfigure redmine. Но я предостерегаю им пользоваться, т.к. при создании нового экземпляра он удаляет настройки уже существующего, не обращая внимания на ваш ответ о том, что вы не хотите это делать.

воскресенье, 28 сентября 2014 г.

Установка Redmine в Debian Wheezy

Эту заметку отчасти можно рассматривать как продолжение заметки Настройка nginx, php5-fpm и uwsgi. Здесь я покажу, как можно запустить Redmine под управлением uwsgi.

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

Написана эта система на языке Ruby и с использованием очень известного за пределами Ruby-мира веб-фреймворка Rails. Запустить дистрибутивный Redmine мне удалось далеко не с первого раза: сначала появлялась какая-то ошибка обработки UTF-8, которую удалось решить переключением с Ruby1.9.1 на Ruby1.8, потом я долго не мог справиться с неполной передачей данных между веб-сервером и uwsgi, которая решилась указанием специальной опции буферизации uwsgi.

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

Для начала установим необходимые пакеты:
# apt-get install redmine redmine-mysql ruby1.8 uwsgi uwsgi-plugin-rack-ruby1.8 nginx-full
Как уже понятно из состава установленных пакетов, в качестве СУБД будем использовать MySQL. В процессе установки запустится мастер настройки базы данных. Ниже приведены снимки экрана с его диалоговыми окнами:







Настройка uwsgi

Для запуска Redmine под управлением uwsgi создадим файл /etc/uwsgi/apps-available/redmine.ini со следующим содержимым:
[uwsgi]

master = true
procname = uwsgi-redmine
procname-master = uwsgi-redmine-master
  
plugin = uwsgi_rack_ruby18
rails = /usr/share/redmine/
env = RAILS_ENV=production
env = RAILS_RELATIVE_URL_ROOT=/redmine
processes = 4
post-buffering = 1
Описание опции post-buffering, с помощью которой удалось решить проблему с неполной передачей данных между веб-сервером и uwsgi, найденное на странице Things to know (best practices and “issues”) READ IT !!!:
If an HTTP request has a body (like a POST request generated by a form), you have to read (consume) it in your application. If you do not do this, the communication socket with your webserver may be clobbered. If you are lazy you can use the post-buffering option that will automatically read data for you. For Rack applications this is automatically enabled.
Несмотря на то, что тут написано что эта опция автоматически включается для Rack-приложений, на практике мне пришлось включить её вручную. Возможно автоматическое включение опции было добавлено в более поздних версиях uwsgi.

Переменная окружения RAILS_RELATIVE_URL_ROOT позволяет настроить Redmine для работы из подкаталога веб-сервера. Если вы запускаете Redmine на отдельном виртуальном или выделенном сервере, то эту строку указывать не нужно.

Осталось включить приложение:
# cd /etc/uwsgi/apps-enabled
# ln -s /etc/uwsgi/apps-available/redmine.ini .
# /etc/init.d/uwsgi start redmine
Настройка веб-сервера

Для начала приведу пример файла конфигурации для настройки Redmine для работы из корня сайта:
server {
  listen 80;

  server_name redmine.domain.tld;

  location / {
    alias /usr/share/redmine/public/;
    try_files $uri @redmine;
  }

  location @redmine {
    uwsgi_pass unix:/var/run/uwsgi/app/redmine/socket;
    include uwsgi_params;

    uwsgi_modifier1 7;
  }
}
Этот файл представляет собой настройки отдельного сайта, поэтому его нужно поместить в отдельный файл в каталоге для сайтов nginx. В данном случае это будет файл /etc/nginx/sites-available/redmine. Чтобы подключить его использование в nginx, нужно создать символическую ссылку:
# cd /etc/nginx/sites-enabled
# ln -s /etc/nginx/sites-available/redmine .
Осталось перезагрузить или перезапустить nginx, чтобы настройки нового сайта вступили в силу:
# /etc/init.d/nginx restart
Можно настроить Redmine для работы из подкаталога /redmine. Для этого нужно вписать следующие настройки в файл конфигурации уже существующего сайта:
location /redmine/ {
    alias /usr/share/redmine/public/;
    try_files $uri @redmine;
  }

  location @redmine {
    uwsgi_pass unix:/var/run/uwsgi/app/redmine/socket;
    include uwsgi_params;

    uwsgi_modifier1 7;
  }
Осталось перезагрузить nginx, чтобы новые настройки вступили в силу:
# /etc/init.d/nginx reload
Redmine

Теперь можно зайти в Redmine:

В системе уже создан пользователь с логином admin и таким же паролем. Не забудьте сразу же после входа поменять пароль!

воскресенье, 7 сентября 2014 г.

Тайловый сервер на основе Python, Mapnik и Bottle

В прошлом я писал серию статей о настройке тайлового сервера на основе apache2, renderd и модуля mod_tile. Недостаток этой связки, с моей точки зрения, заключается во-первых в необходимости использования apache, а во-вторых в том, что renderd и mod_tile отсутствуют в репозиториях Debian. Из-за второго недостатка нет уверенности в том, что при очередном обновлении её удастся успешно собрать и продолжить использовать.

Несколько месяцев назад мне попалась статья Дениса Рыкова Основы работы динамических TMS-сервисов, в которой приводится пример тайлового сервера, написанного на Python с использованием веб-фреймворка Bottle и библиотеки Mapnik. Статья привлекла моё внимание ещё потому, что в последних проектах я начал пользоваться фреймворком Bottle. Этот фреймворк обладает минимальным количеством зависимостей и может всё, что мне нужно от веб-фреймворков (да, мои потребности не велики), не навязывая при этом собственный стиль разработки, как это происходит, например, с Django.

Я решил воспользоваться этой статьёй для того, чтобы избавить себя от возможных проблем с используемой мной связкой на основе apache2, renderd и mod_tile.

1. Тайловый сервер

Для начала установим библиотеку Mapnik и веб-фреймворк Bottle:
# apt-get install python-mapnik2 python-bottle
Сам тайловый сервер разместим в файле /usr/local/share/tiles/main.py:
#!/usr/bin/python
# -*- coding: UTF-8 -*-

from bottle import get, response, run, debug, app, HTTPError
import mapnik2 as mapnik
import os

from settings import TILE_WIDTH, TILE_HEIGHT, BBOX_MINX, BBOX_MINY, BBOX_MAXX, BBOX_MAXY, MAPNIK_CONFIGS, CACHE_PATH

# Сохранение тайла в файловую систему в целях кэширования
def save_tile(filename, body):
    path, name = os.path.split(filename)
    try:
        os.makedirs(path)
    except:
        pass
    file = open(filename, 'w')
    file.write(body)
    file.close() 
 
@get('/<service>/1.0.0/<layer>/<z:int>/<x:int>/<y:int>.png')
def tms(service, layer, z, x, y):
    # Считаем охват тайла на выбранном масштабном уровне
    stepx = (BBOX_MAXX - 1.0 - BBOX_MINX) / (2 ** z)
    stepy = (BBOX_MAXY - 1.0 - BBOX_MINY) / (2 ** z)

    # Выбираем охват тайла из словаря extents согласно типу запрашиваемого сервиса (tms или xyz)
    if service == 'tms':
        box = mapnik.Box2d(BBOX_MINX + x * stepx,
                           BBOX_MINY + y * stepy,
                           BBOX_MINX + (x + 1) * stepx,
                           BBOX_MINY + (y + 1) * stepy)
    elif service == 'xyz':
        box = mapnik.Box2d(BBOX_MINX + x * stepx,
                           BBOX_MAXY - (y + 1) * stepy,
                           BBOX_MINX + (x + 1) * stepx,
                           BBOX_MAXY - y * stepy)
 
    # Указываем путь, где находится файл с настройками Mapnik
    map = mapnik.Map(TILE_WIDTH, TILE_HEIGHT)
    mapnik.load_map(map, os.path.join(MAPNIK_CONFIGS, layer + '.xml'))
 
    # Нацеливаем карту на выбранный охват
    map.zoom_to_box(box)
 
    # Отрисовываем тайл
    image = mapnik.Image(map.width, map.height)
    mapnik.render(map, image)
    body = image.tostring('png')

    # Сохраняем тайл в файловой системе
    filename = os.path.join(CACHE_PATH, service, '1.0.0', layer, str(z), str(x), str(y) + '.png')
    save_tile(filename, body)

    # Передаём ответ клиенту
    response.content_type = 'image/png'
    return body

if __name__ == '__main__':
    debug(True)
    run(host = '0.0.0.0', port = 8080)
else:
    application = app()
В отдельном файле /usr/local/share/tiles/settings.py разместим настройки этого приложения:
#!/usr/bin/python
# -*- coding: UTF-8 -*-

# Размеры тайла
TILE_WIDTH = 256
TILE_HEIGHT = 256

# Размеры карты
BBOX_MINX = -20037508.0
BBOX_MINY = -20037508.0
BBOX_MAXX = 20037508.0
BBOX_MAXY = 20037508.0

# Каталог с xml-файлами с описаниями слоёв
MAPNIK_CONFIGS = '/etc/mapnik-osm-data/'
# Каталог для хранения кэша тайлов
CACHE_PATH = '/var/cache/tiles'
Таким образом, в запросе можно указывать имя слоя, тайл из которого нужно вернуть. Имя слоя преобразуется в имя XML-файла, из которого извлекается его описание. Например, слою OpenStreetMap будет соответствовать запрос http://tiles.domain.tld/xyz/1.0.0/osm/0/0/0.png, при обслуживании которого будет использован файл /etc/mapnik-osm-data/osm.xml.

Создадим каталог для кэша тайлов:
# mkdir /var/cache/tiles
Проставим права доступа к нему для веб-сервера:
# chown www-data:www-data /var/cache/tiles
2. Сервер приложений uwsgi

Установим uwsgi и плагин к нему uwsgi-plugin-python, если они ещё не установлены:
# apt-get install uwsgi uwsgi-plugin-python
Создадим файл конфигурации /etc/uwsgi/apps-available/tiles.ini:
[uwsgi]

procname = uwsgi-tiles
procname-master = uwsgi-tiles-master

chdir = /usr/local/share/tiles
#module = app:application
mount = /=main:application
plugin = python27
master = true
processes = 8
Добавим файл конфигурации в каталог включенных приложений:
# cd /etc/uwsgi/apps-enabled
# ln -s /etc/uwsgi/apps-available/tiles.ini .
Запустим новое приложение:
# /etc/init.d/uwsgi start tiles
3. Веб-сервер nginx

Установим nginx, если он ещё не установлен:
# apt-get install nginx-full
Для доступа к файлам в кэше и запросе файла у тайлового сервера при их отсутствии в кэше создадим файл конфигурации. Например, если этот веб-сервер будет использоваться только для отрисовки тайлов, можно создать файл /etc/nginx/sites-available/default со следующим содержимым:
server {
  listen 0.0.0.0:80;
#  server_name tiles.domain.tld;

  root /var/www;

  location @tiles {
    uwsgi_pass unix:/var/run/uwsgi/app/tiles/socket;
    include uwsgi_params;
  }

  location /tms/ {
    root /var/cache/tiles/;
    try_files $uri @tiles;
  }

  location /xyz/ {
    root /var/cache/tiles/;
    try_files $uri @tiles;
  }
}
Включим использование этого файла веб-сервером:
# cd /etc/nginx/sites-enabled
# ln -s /etc/nginx/sites-available/tiles .
Перезапустим nginx, чтобы его новые настройки вступили в силу:
# /etc/init.d/nginx restart
4. Очистка кэша

Осталось только настроить периодическую чистку кэша от тайлов, к которым давно не было обращений. Это позволит достичь более высокой эффективности кэша в пересчёте на его объём. Сделать это просто - можно воспользоваться обычыми для Unix инструментами - планировщиком задач cron и командой find. Добавим в файл /etc/crontab такую строчку:
30    8 * * *   www-data     find /var/cache/tiles -type f -atime 14 -delete ; find /var/cache/tiles -type d -empty -delete
Из каталога кэша тайлов будут удаляться файлы, к которым не обращались более 14 дней, а также пустые каталоги.

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

воскресенье, 24 августа 2014 г.

Настройка nginx, php5-fpm и uwsgi

До недавнего времени я долгое время пользовался веб-сервером Lighttpd, который меня во всём устраивал. Но сейчас я стал пользоваться nginx, хотя сам nginx на такой выбор непосредственно не повлиял. Но, обо всём по порядку.

1. Менеджер процессов php5-fpm

В начале года я заинтересовался менеджером процессов php5-fpm для обслуживания приложений на PHP. Он обладает большим количеством приятных преимуществ, основанных на так называемых пулах. Пул - это группа процессов, выделенная для обработки запросов, поступающих на определённый порт или Unix-сокет. Для каждого пула действует своя политика управления процессами:
  • static - строго постоянное количество процессов-обработчиков,
  • dynamic - переменное количество обработчиков, для которых указывается минимальное и максимальное количество процессов, а также количество процессов-обработчиков "на подхвате", которые держатся готовыми на случай внезапного наплыва нагрузки, чтобы не терять время на порождение новых процессов-обработчиков,
  • ondemand - режим, при котором обработчики порождаются только при поступлении запросов и завершаются спустя указанный период простоя.
Каждый пул может быть запущен от имени отдельного пользователя, так что можно легко изолировать несколько приложений друг от друга. Кроме того, даже если пулы работают от имени одного и того же пользователя, разделение приложений по пулам позволяет предотвратить ситуацию, когда высоконагруженное приложение постоянно держит занятыми процессы-обработчики, не давая таким образом нормально работать лёгким интерактивным приложениям.

Ну и самое приятное заключается в том, что для каждого из пулов можно задавать разные настройки PHP или вообще строго фиксировать их, запрещая приложению менять эти настройки самостоятельно. Например, какое-то приложение может требовать для работы много памяти (веб-интерфейс Zabbix, например), другое может долго формировать страницу (например, какое-то веб-приложение, формирующее отчёт), третьему нужно ограничить таймауты подключения к базе данных или время ожидания данных из сокета. В таком случае каждому приложению можно выделить отдельный пул и задать отдельные настройки.

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

Однако, php5-fpm можно хорошо использовать и совместно с Lighttpd. Lighttpd даже в чём-то проще. Например, при совместной работе php5-fpm и Lighttpd мне не приходилось настраивать размеры буферов для чтения заголовков ответов, что может понадобиться сделать при использовании nginx и какого-то приложения, устанавливающего большое количество cookie. Если вам требуется запускать только приложения, написанные на PHP, то связка Lighttpd и php5-fpm может оказаться оптимальным выбором, особенно если вы уже знакомы с Lighttpd.

2. Сервер uwsgi

Позже мне потребовалось запускать веб-приложения, написанные на веб-фреймворках на языках Python и Perl. В этих веб-фреймворках приложение в конечном итоге представляет собой объект, поддерживающий интерфейсы WSGI или PSGI соответственно. Я уже пользовался веб-серверами, которые реализуют HTTP-интерфейс для приложений WSGI и PSGI - это были flup и starman. Оба веб-сервера мне не понравились своей требовательностью к ресурсам, поскольку они написаны на соответствующих интерпретируемых языках, а также необходимостью под каждое веб-приложение создавать отдельный скрипт инициализации. Этих проблем был лишён найденный сервер uwsgi, который написан на Си, имеет удобный сценарий инициализации и позволяет запускать веб-приложения с интерфейсом WSGI и PSGI. С его помощью можно даже запускать приложения Rack, написанные на Ruby. Один недостаток - снаружи он предоставляет не HTTP-интерфейс, а всё тот же WSGI.

Совместно с uwsgi умеет работать nginx, так что выбор такой связки был предопределён. Осталось решить последний вопрос - как запускать классические CGI-приложения? Поначалу я думал воспользоваться сервером spawn-fcgi или fcgiwrap, но потом нашёл CGI-плагин для uwsgi, так что для запуска любого веб-приложения оказалось достаточно лишь php5-fpm и uwsgi, которыми ещё и очень удобно управлять. Несмотря на то, что в uwsgi имеется возможность обслуживать запросы к приложениям на PHP, я всё же предпочёл использовать для PHP сервер php5-fpm, обладающий большим количеством настроек, позволяющих получить больший контроль над PHP-приложениями.

Именно таким образом я и пришёл к решению сменить привычный мне веб-сервер Lighttpd на связку nginx + php5-fpm + uwsgi. В 2008 я перешёл от использования Apache на Lighttpd, в 2014 я сменил Lighttpd на nginx. С тех пор понимание того, каким должен быть хороший веб-сервер кардинально изменилось. Если раньше это был комбайн, в котором было довольно тяжело ориентироваться, то теперь это несколько взаимодействующих программ, каждая из которых очень наглядна, быстро осваивается, радует удобством и скоростью работы.

3. Установка пакетов

Если вы планируете использовать nginx только совместно с php5-fpm, то установить потребуется только два пакета - легкий nginx и сам менеджер процессов:
# apt-get install nginx-lite php5-fpm
Если же вы планируете использовать uwsgi, то вам нужна полная версия nginx и uwsgi-сервер:
# apt-get install nginx-full uwsgi
Дополнительно, в зависимости от того, какие приложения вы собираетесь запускать под управлением uwsgi, вам может понадобиться один или несколько дополнительных пакетов. Для поддержки выполнения CGI-скриптов при помощи uwsgi установите пакет с соответствующим модулем:
# apt-get install uwsgi-plugin-cgi
Если вы планируете запускать Perl-приложения с интерфейсом PSGI, то вам понадобится модуль для поддержки соответствующего протокола:
# apt-get install uwsgi-plugin-psgi
Наконец, uwsgi можно использовать и для запуска WSGI-приложений, написанных на Python. Для этого нужен модуль, называющийся... python:
# apt-get install uwsgi-plugin-python
4. Заготовка файла конфигурации nginx

Создадим заготовку файла конфигурации /etc/nginx/sites-enabled/default:
server {
  listen 0.0.0.0:80;
  # server_name info.domain.tld;

  root /var/www;
  index index.html index.php index.pl;
}
В последующих разделах приводятся небольшие фрагменты, которые можно добавить внутрь секции server этой заготовки.

Здесь настраивается виртуальный сервер, принимающий подключения на TCP-порту 80 на любой настроенный на компьютере IP-адрес. Если необходимо выделить несколько виртуальных веб-серверов, работающих на разных IP-адресах, можно создать несколько секций, указав в каждой из них соответствующий IP-адрес.

Если необходимо запустить на одном IP-адресе виртуальные серверы, отличающиеся доменным именем, то можно раскомментировать опцию server_name и указать одно или несколько доменных имён через пробел. Один из таких серверов можно отметить как сервер по умолчанию при помощи ключевого слова default в опции listen в самом её конце - на него будут поступать запросы, для которых не удалось найти секцию с подходящим доменным именем, но поступившие на этот IP-адрес.

Опция root задаёт каталог, из которого будут браться запрошенные файлы. Если имя конкретного файла не указано, а указан лишь каталог, то будут перебираться по очереди имена, указанные в опции index. Первый же существующий файл будет отдан клиенту или обработан как приложение (в зависимости от настроек из последующих секций).

5. CGI-приложения на PHP

Для выполнения приложений на PHP воспользуемся уже упомянутым сервером php-fpm. Создадим файл с описанием пула, который будет обрабатывать запросы к PHP-файлам. Для этого создадим файл /etc/php5/fpm/pool.d/default.conf или приведём имеющийся файл к следующему виду:
; Имя пула
[default]

; Рабочие процессы пула будут работать от имени указанного пользователя и группы
user = www-data
group = www-data

; Пул будет ожидать запросы на указанном Unix-сокете
listen = /var/run/php.sock

; Владелец Unix-сокета, его группа и права доступа к сокету
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Динамический менеджер рабочих процессов будет поддерживать от 10 до 30 процессов,
; из которых от 5 до 10 могут простаивать в ожидании поступления новых запросов.
; Если простаивающих процессов будет меньше 5 - будут порождены новые процессы,
; если простаивающих процессов окажется больше 10 - лишние будут завершены
pm = dynamic
pm.max_children = 30
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 10

; Тут можно ограничить количество запросов, последовательно обслуживаемых одним процессом
; После этого процесс будет завершён и запущен снова - это может помочь от утечек памяти
;pm.max_requests = 500

; Если обработка одного запроса длится дольше трёх минут - обработка запроса принудительно завершается
request_terminate_timeout = 180s

; В этом файле можно вести журнал обработанных запросов
;access.log = /var/log/php5-fpm.access.log

; Тут можно задать настройки, которые обычно указывают в файле php.ini
; Разница в том, что эти настройки будут действовать не глобально, а только внутри пула
; Настройки php_admin нельзя поменять изнутри самого PHP-приложения
php_value[data.timezone] = Asia/Yekaterinburg
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php5-fpm.errors.log
;php_admin_value[memory_limit] = 128M
php_admin_value[mysql.connect_timeout] = 1
php5-fpm умеет перечитывать файлы конфигурации, соответствующим образом меняя состав пулов и их настройки, не трогая те пулы, настройки которых не поменялись:
# /etc/init.d/php5-fpm reload
Однако, если вам кажется, что настройки не вступили в силу, можно перезапустить php5-fpm целиком, чтобы он гарантированно прочитал и применил новые настройки пулов, полностью перезапустив их процессы:
# /etc/init.d/php5-fpm restart
Теперь нужно добавить в заготовку файл, например, со следующим содержимым:
location ~ ^/php/(base|index|logout|query)\.php$ {
  fastcgi_pass unix:/var/run/php.sock;
  include fastcgi_params;
}

location /php/static/ {
  alias /usr/local/share/php/static/;
}
В первой секции location перечисляются файлы, запросы к которым будут обрабатываться созданным нами пулом процессов php5-fpm. Во второй секции location указан путь к статическим файлам, отдачей которых будет заниматься сам nginx.

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

Осталось перезапустить nginx, чтобы его новые настройки вступили в силу:
# /etc/init.d/nginx restart
nginx тоже умеет перезагружать обновлённую конфигурацию, однако рабочие процессы могут некоторое время продолжать работать со старой конфигурацией, пока не будут вытеснены новыми. Если это не критично, то перезагрузить его можно так:
# /etc/init.d/nginx reload
6. PHP-приложения на фреймворке CodeIgniter

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

Прежде чем приступить к настройке nginx, сначала настроим само приложение - в файле application/config/config.php нужно прописать следующие настройки:
$config['base_url'] = '';
$config['index_page'] = '';
$config['uri_protocol'] = 'REQUEST_URI';
Первые две настройки не заданы, т.к. будет использоваться автоматическое их определение самим фреймворком.

Теперь пришла очередь nginx. Для обработки запросов к приложению будем использовать пул php5-fpm, настройка которого была описана в предыдущем разделе. В нашу заготовку файла конфигурации nginx /etc/nginx/sites-enabled/default.conf добавим следующие настройки:
location /ci/ {
  fastcgi_pass unix:/var/run/php.sock;
  include fastcgi_params;

  fastcgi_buffer_size 64k;
  fastcgi_buffers 8 64k;

  fastcgi_param SCRIPT_FILENAME /usr/local/share/ci/index.php;
  fastcgi_param SCRIPT_NAME /ci/index.php;

  fastcgi_read_timeout 120s;
}

location /ci/data/ {
  alias /usr/local/share/ci/data/;
}
В первой секции переопределяются некоторые настройки, значения по умолчанию которых нас не устраивают. Важно переопределять их после включения файла с настройками по умолчанию - иначе он сам переопределит заданные нами настройки.

Размеры буферов переопределены, поскольку CodeIgniter интенсивно использует cookie - заголовок запроса или ответа может получиться очень большим и не поместиться в буфер. В этом случае приложение может иногда работать, а иногда - не работать, в зависимости от того, какой объём имеют cookie.

Следующие опции указывают, какой файл будет обрабатывать запросы и помогут фреймворку автоматически определить значения опций base_url и index_page.

Наконец, последняя настройка указывает nginx'у, что ждать ответа от php5-fpm не стоит больше двух минут. В пуле php5-fpm определён таймаут в 3 минуты, так что в данном случае nginx отключится от php5-fpm первым, не дождавшись срабатывания таймаута в процессе-обработчике.

Не забудьте перезагрузить или перезапустить nginx:
# /etc/init.d/nginx restart
7. CGI-приложения на Perl

Запросы к CGI-приложениям может обслуживать uwsgi с модулем uwsgi-plugin-cgi. В uwsgi нет понятия пулов и нет единственного мастер-процесса, который бы шефствовал над остальными процессами. Вместо этого можно определить несколько раздельных файлов конфигурации, у каждого из которых будет собственный мастер-процесс. Централизованное управление мастер-процессами на данный момент осуществляется при помощи единого init-скрипта.

В новых версиях uwsgi появился режим emperor, который работает полностью аналогично php5-fpm - с одним мастер-процессом. Более того - он умеет обнаруживать появление новых файлов приложений или изменения в имеющихся файлах, поддерживая состав и настройки рабочих процессов актуальными. Однако, в Debian Wheezy эта новая версия uwsgi ещё не попала, поэтому я опишу текущее положение дел.

Определим настройки приложения в файле /etc/uwsgi/apps-enabled/cgi.ini:
[uwsgi]

; Имена рабочих процессов приложения и мастер-процесса
procname = uwsgi-cgi
procname-master = uwsgi-cgi-master

; Используемый плагин и его настройки: корень с файлами, обрабатываемые расширения,
; максимальное время обработки запроса скриптом
plugins = cgi
cgi = /var/www
cgi-allowed-ext = .pl
cgi-timeout = 120

; Рабочих процессов будет четыре - можно будет одновременно обслуживать не более 4 запросов,
; а последующие запросы будут вставать в очередь до освобождения одного из рабочих процессов
processes = 4
Теперь можно перезапустить все приложения или индивидуально - только что созданное:
# /etc/init.d/uwsgi restart
# /etc/init.d/uwsgi start cgi
После запуска нового приложения uwsgi создаст Unix-сокет /var/run/uwsgi/app/cgi/socket, где cgi - имя приложения. Кроме того, будет создан журнал работы приложения в файле /var/log/uwsgi/app/cgi.log, где cgi - опять то же имя приложения.

Задаём настройки nginx, вписав в заготовку файла конфигурации ещё один фрагмент:
location = /pl/index.pl {
  uwsgi_pass unix:/var/run/uwsgi/app/cgi/socket;
  include uwsgi_params;

  # Модификатор для протокола CGI
  uwsgi_modifier1 9;
  # Скрипт, обрабатывающий запросы
  uwsgi_param SCRIPT_FILENAME /usr/local/share/pl/index.pl;
}

location /pl/static/ {
  alias /usr/local/share/pl/static/;
}
Значение опции uwsgi_modifier1 берётся из таблицы на странице The uwsgi Protocol. В данном случае используется протокол CGI, значение модификатора - 9.

Не забудьте перезагрузить или перезапустить nginx.

8. Perl с фреймворком Dancer

Для запуска PSGI-приложений под управлением uwsgi нужен файл, возвращающий объект PSGI. Для этого создадим файл приложения /usr/local/share/dancer/bin/app.psgi:
#!/usr/bin/perl

use Dancer;

...

setting apphandler => 'PSGI';

my $app = sub {
    my $env = shift;
    my $request = Dancer::Request->new(env => $env);
    Dancer->dance($request);
};
На месте многоточия располагается тело приложения, где определяются обработчики страниц.

Теперь создадим файл конфигурации веб-приложения /etc/uwsgi/apps-enabled/dancer.ini со следующим содержимым:
[uwsgi]

; Имена рабочих процессов приложения и мастер-процесса
procname = uwsgi-dancer
procname-master = uwsgi-dancer-master

; Настраиваем плагин и задаём количество рабочих процессов
chdir = /usr/local/share/dancer
plugin = psgi
psgi = /usr/local/share/dancer/bin/app.psgi
; Приложение используется редко - запустим два рабочих процесса
processes = 2
Запустим новое веб-приложение:
# /etc/init.d/uwsgi start dancer
В заготовку конфигурации nginx добавим следующий фрагмент:
location ~ ^/dancer/ {
  uwsgi_pass unix:/var/run/uwsgi/app/dancer/socket;
  include uwsgi_params;
  
  # Указываем модификатор для использования протокола PSGI
  uwsgi_modifier1 5;
}

location /dancer/static/ {
  alias /usr/local/share/dancer/static/;
}
Осталось перезапустить или перезагрузить nginx.

9. Python и приложение на Django в подкаталоге

При создании проекта Django генерируется файл wsgi.py, внутри которого создаётся WSGI-объект application. Для запуска Django-приложения через uwsgi создадим файл /etc/uwsgi/apps-enabled/dj1.ini с конфигурацией приложения:
[uwsgi]

; Имена рабочих процессов приложения и мастер-процесса
procname = uwsgi-dj1
procname-master = uwsgi-dj1-master

; В пакете на самом деле два плагина - python26 и python27, выбираем плагин python27
plugin = python27
; Каталог проекта Django
chdir = /usr/local/share/dj1
; Используем файл wsgi.py, в котором определяется WSGI-приложение application
module = wsgi:application
; Запустим шесть рабочих процессов
processes = 6
Запустим рабочие процессы приложения:
# /etc/init.d/uwsgi start dj1
А в заготовку конфигурации nginx добавим такой фрагмент:
location /dj1/ {
  uwsgi_pass unix:/var/run/uwsgi/app/dj1/socket;
  include uwsgi_params;

  # Этот модификатор используется для протокола WSGI
  uwsgi_modifier1 30;
  # Имя скрипта будет вырезаться из URL перед маршрутизацией запроса
  uwsgi_param SCRIPT_NAME /dj1;
}

location /dj1/static/ {
  alias /usr/local/share/dj1/static/;
}
Осталось перезагрузить или перезапустить nginx.

10. Python и приложение на Django в корне

Создадим файл /etc/uwsgi/apps-enabled/dj2.ini с конфигурацией приложения (здесь всё аналогично предыдущему случаю):
[uwsgi]

; Имена рабочих процессов приложения и мастер-процесса
procname = uwsgi-dj2
procname-master = uwsgi-dj2-master

; В пакете на самом деле два плагина - python26 и python27, выбираем плагин python27
plugin = python27
; Каталог проекта Django
chdir = /usr/local/share/dj2
; Используем файл wsgi.py, в котором определяется WSGI-приложение application
module = wsgi:application
; Запустим шесть рабочих процессов
processes = 6
Запустим рабочие процессы приложения:
# /etc/init.d/uwsgi start dj2
В заготовку конфигурации nginx добавляем фрагмент:
# Здесь нужно перечислить все корневые страницы или подкаталоги со страницами приложения
location ~ ^/(admin|login|logout|app)(/|$) {
  uwsgi_pass unix:/var/run/uwsgi/app/dj2/socket;
  include uwsgi_params;
}

# Каталог со статическими файлами
location /static/ {
  alias /usr/local/share/dj2/static/;
}

# Каталог со статическими файлами для админ-панели Django
location /static/admin/ {
  alias /usr/lib/python2.7/dist-packages/django/contrib/admin/static/admin/;
}
Особенность в том, что модификатор в данном случае не используется, а в первой директиве location нужно перечислить все страницы, обработку которых нужно передавать в Django-приложение. Впрочем, если кроме этого приложения на сервере больше ничего нет и не будет, можно создать секцию для обработки корневого каталога.

Осталось перезагрузить или перезапустить nginx.

11. Python и веб-фреймворк Bottle

В случае с фреймворком Bottle всё аналогично Django. Разница лишь в том, что в Bottle нет генератора проекта, а потому создать объект WSGI-приложения придётся самостоятельно. Если же все маршруты вашего проекта определены в приложении по умолчанию, то достаточно в начало головного файла добавить импорт функции app из модуля фреймворка:
from bottle import app
И затем использовать эту функцию для создания экземпляра WSGI-объекта приложения. Для этого создадим файл /etc/uwsgi/apps-enabled/bottle.ini:
[uwsgi]

; Имена рабочих процессов приложения и мастер-процесса
procname = uwsgi-bottle
procname-master = uwsgi-bottle-master

; В пакете на самом деле два плагина - python26 и python27, выбираем плагин python27
plugin = python27
; Каталог приложения
chdir = /usr/local/share/bottle
; Используем файл main.py, в котором есть функция app, возвращающая экземпляр WSGI-приложения
module = main:app()
; Задаём количество рабочих процессов
processes = 2
Запустим рабочие процессы приложения:
# /etc/init.d/uwsgi start bottle
nginx настраивается аналогично одному из примеров для приложений Django, в зависимости от того, будет ли приложение доступно из каталога или из корня URL.

12. О безопасности

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

воскресенье, 15 июня 2014 г.

Настройка WiFi

До сих пор у меня совершенно отсутствовал опыт работы с беспроводными сетями. Однако, всё оказалось не столь трудно, как могло бы показаться.

Для организации домашней беспроводной сети я выбрал беспроводной адаптер TP-Link TL-WN781ND с разъёмом PCI-E. Адаптер собран на базе чипа AR9485 от Atheros, которые стали поддерживаться в Linux одними из первых.

Немного теории

Когда оборудование было выбрано, настало время немного узнать об основах WiFi. WiFi - это торговая марка, обозначающая большую группу стандартов IEEE 802.11. Среди них можно выделить несколько отдельных стандартов, которые обозначаются буквенными индексами:
  • a - скорость до 54 Мбит/с, частоты 5150-5350 МГц, каналы 34-64, модуляция DSSS,
  • b - скорость до 11 Мбит/с, частоты 2400-2483,5 МГц, каналы 1-13, модуляция OFDM,
  • g - скорость до 54 Мбит/с, частоты 2400-2483,5 МГц, каналы 1-13, модуляция DSSS/OFDM,
  • n - скорость от 150 до 600 Мбит/с, в зависимости от количества антенн (от 1 до 4). Базируется на стандартах a, b и g.
Отдельно я поинтересовался вопросами безопасности. Существуют следующие алгоритмы обеспечения безопасности:
  • WEP - слабая стойкость алгоритма, может быть легко взломан,
  • WPA - более сильный алгоритм, работающий на старом оборудовании. Для внедрения этого алгоритма было достаточно обновления прошивки оборудования, поддерживающего WEP. Использует алгоритм шифрования TKIP. В настоящее время тоже легко поддаётся взлому,
  • WPA2 - новый алгоритм, для использования которого требуется специальная аппаратная поддержка, которая имеется в современном оборудовании. Использует алгоритм шифрования CCMP на основе блочного алгоритма шифрования AES. Рекомендуется использовать именно этот алгоритм.
Далее, существует два протокола аутентификации:
  • WPA-PSK/WPA2-PSK - используется общий для клиента и точки доступа ключ. Пригоден для применения в домашней сети, т.к. не требует настройки дополнительных серверов,
  • WPA-EAP/WPA2-EAP - используется протокол EAP, который, как правило, работает совместно с сервером RADIUS, осуществляющим аутентификацию, авторизацию и учёт сеансов. Этот протокол имеет множество вариантов аутентификации и используется, как правило, в больших корпоративных сетях.
Итак, если принять во внимание всё описанное, то в моём случае стоит использовать режим n (или хотя-бы g) и протокол WPA2-PSK.

Настройка точки доступа

Для настройки точки доступа воспользуемся пакетом hostapd. Установим его:
# apt-get install hostapd
В пакете поставляется пример файла конфигурации с подробными комментариями: /usr/share/doc/hostapd/examples/hostapd.conf.gz

Создадим файл конфигурации /etc/hostapd/hostapd.conf:
interface=wlan0            # Настраиваемый беспроводной интерфейс
bridge=br0                 # Включить беспроводной интерфейс в указанный интерфейс-мост

driver=nl80211             # Используемый драйвер
country_code=RU            # Код нашей страны
ieee80211d=1               # Включаем использование ограничений каналов и мощности сигнала,
                           # принятые в нашей стране
ieee80211n=1               # Включить режим n в дополнение к основному

ssid=stupin.su             # Идентификатор сети
hw_mode=g                  # Основной режим g
channel=1                  # Канал 1 (можно поэкспериментировать, задавая значения от 1 до 11)

auth_algs=1                # Использовать только алгоритмы аутентификации WPA2

wpa=2                      # Использовать только WPA2
wpa_key_mgmt=WPA-PSK       # Используем общий ключ
wpa_passphrase=passphrase  # Кодовая фраза, используемая в качестве общего ключа
wpa_pairwise=CCMP          # Используемый алгоритм шифрования
Настройку bridge можно не указывать, если вы не хотите объединять беспроводной интерфейс с Ethernet-интерфейсом в мостовой интерфейс. Я это сделал, поскольку у меня имеются проводные устройства, которые я хочу объединить в одну сеть с беспроводными.

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

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

Вместо настройки wpa_passphrase можно использовать настройку wpa_psk, в которой указывается шестнадцатеричная последовательность, представляющая собой общий ключ, или настройку wpa_psk_file, в которой можно указывать разные ключи для разных устройств, сопоставляя их MAC-адресам устройств. Подробнее об этом можно почитать в примере файла конфигурации, идущего в составе пакета hostapd.

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

Отберём у пользователей системы возможность заглядывать в файл конфигурации, чтобы они не подсмотрели пароль (просто пятиминутка паранойи, вы можете оставить файл доступным на чтение всем):
# chmod o= /etc/hostapd/hostapd.conf
Теперь можно перезапустить hostapd, чтобы он начал анонсировать идентификатор сети и клиенты смогли подключиться к сети:
# /etc/init.d/hostapd start
К слову о паранойе. Можно запретить анонсировать точке доступа идентификатор сети, так что к сети можно будет подключиться только зная её идентификатор. Однако, в процессе подключения идентификатор сети всё равно будет передан точке доступа, так что злоумышленник сможет его перехватить. Я не запрещаю анонсы идентификатора сети, т.к. существенно безопасность при этом не увеличится, а отлаживать неполадки станет сложнее, т.к. без анонса сложно будет определить - а работает ли вообще точка доступа?

Настройка клиента

Для полного комплекта рассмотрим ещё настройку клиентского беспроводного соединения. Для этого воспользуюсь ноутбуком со встроенной WiFi-картой на основе того же чипа AR9485. Для начала установим пакет wpasupplicant:
# apt-get install wpasupplicant
В Debian настройка беспроводных соединений, как и многих других, органично вписывается в общую систему конфигурирования сети. Отдельно настраивать wpa_supplicant не требуется, т.к. все необходимые настройки можно вписать в файл /etc/network/interfaces:
allow-hotplug wlan0
iface wlan0 inet dhcp       # Настройки сети получим от DHCP-сервера
  wpa-ssid stupin.su        # Идентификатор сети
  wpa-ap-scan 1             # Идентификатор сети анонсируется точкой доступа
  wpa-proto RSN             # Используем "усиленный" WPA, фактически - WPA2
  wpa-pairwise CCMP         # Используемый алгоритм шифрования
  wpa-key-mgmt WPA-PSK      # Используемый алгоритм аутентификации
  wpa-passphrase passphrase # Кодовая фраза, используемая в качестве общего ключа
Осталось поднять интерфейс:
# ifup wlan0
Если вы бываете в нескольких разных местах, где работают разные беспроводные сети, можно воспользоваться пакетом guessnet, который умеет обнаруживать беспроводные сети по их идентификаторам и MAC-адресам точек доступа. Я оставлю описание настройки guessnet за рамками этой статьи.

К сожалению, даже совместно с guessnet и ifplugd, мне не удалось настроить правильную обработку кнопки включения-выключения беспроводных устройств, имеющихся в ноутбуке. После повторного включения появляется две копии DHCP-клиента, так что сеть не удаётся настроить без захода под пользователем root.

Кроме ноутбука я пробовал подключать к сети планшет, сотовый телефон и цифровой мультимедийный проигрыватель. С мультимедийным проигрывателем воспроизведение видео иногда замирало или "рассыпалось на кубики", так что для просмотра видео стоит всё-же использовать проводную Ethernet-сеть, с которой таких проблем не бывает.

Ссылки

воскресенье, 8 июня 2014 г.

Postfix как локальный SMTP-ретранслятор

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

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

Для начала установим необходимые пакеты:
# apt-get install postfix libsasl2-modules bsd-mailx
Для отправки почты с подменой отправителя и аутентификацией на сервере провайдера можно настроить Postfix. Настроим в файле /etc/postfix/main.cf следующую конфигурацию:
# Имя сервера и его почтового домена
myhostname = server.domain.tld
mydomain = domain.tld

# Откуда и для кого принимать почту к доставке
inet_protocols = ipv4
inet_interfaces = 127.0.0.1
mydestination = $myhostname, localhost.$mydomain, localhost
mynetworks = 127.0.0.0/8

# К адресам локальных отправителей добавлять этот домен
masquerade_domains = domain.tld

# Карта соответствия локальных получателей адресам на почтовом сервере ISP  
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases

# Карта соответствия локальных отправителей ящикам на почтовом сервере ISP  
sender_canonical_maps = hash:/etc/postfix/sender_maps

# Включаем использование аутентификации на сервере провайдера
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/passwords

# Включаем выбор учётных данных на сервере провайдера в зависимости от отправителя
smtp_sender_dependent_authentication = yes
sender_dependent_relayhost_maps = hash:/etc/postfix/sender_relays

# Если в таблице отправителей нет соответствия, использовать этот почтовый сервер ISP
relayhost = [mailserver.domain.tld]:587

# Устанавливаем не более одного исходящего подключения на каждый домен
default_destination_concurrency_limit = 1

# Разрешаем использовать механизмы аутентификации PLAIN и LOGIN
smtp_sasl_security_options = noanonymous
smtp_sasl_tls_security_options = noanonymous
smtp_sasl_mechanism_filter = plain, login
После настройки самого Postfix, его можно перезапустить, чтобы настройки вступили в силу:
# /etc/init.d/postfix restart
Теперь создадим необходимые карты. Укажем в файле /etc/aliases, на какие ящики на сервере провайдера перенаправлять почту для локальных пользователей:
postmaster root
root recipient@domain.tld
Почта для пользователя root будет перенаправляться в ящик recipient@domain.tld.

Укажем в файле /etc/postfix/sender_maps, какой ящик на сервере провайдера использовать для отправки почты от локального отправителя:
root sender@domain.tld
Когда пользователь root попытается отправить письмо, то на сервер провайдера оно уйдёт от отправителя sender@domain.tld.

Укажем в файле /etc/postfix/sender_relays, какой сервер следует использовать для отправки писем от определённого отправителя:
sender@domain.tld [mailserver.domain.tld]:587
Когда пользователь root попытается отправить почту, письмо, в соответствии с настройками в файле /etc/postfix/sender_maps, будет отправлено с адреса sender@domain.tld. Письмо от этого отправителя нужно отправить через порт 587 сервера mailserver.domain.tld.

Укажем в файле /etc/postfix/passwords учётные данные каждого из ящиков на сервере провайдера:
sender@domain.tld sender:password
Когда пользователь root попытается отправить почту, письмо, в соответствии с настройками в файле /etc/postfix/sender_maps, будет отправлено с адреса sender@domain.tld. Письмо от этого отправителя, в соответствии с настройками в файле /etc/postfix/sender_relays, нужно отправить через порт 587 сервера mailserver.domain.tld. В соответствии с настройками в этом файле для аутентификации на сервере провайдера нужно будет использовать имя пользователя sender и пароль password.

Для файла /etc/postfix/passwords стоит задать разрешения, ограничивающие возможность подсмотреть пароли локальными пользователями системы:
# chown root:root /etc/postfix/passwords
# chmod ug=rw,o= /etc/postfix/passwords
При каждом обновлении файлов карт нужно не забывать обновлять их двоичные копии одной из следующих команд:
# postalias /etc/aliases
# postmap /etc/postfix/sender_maps
# postmap /etc/postfix/sender_relays
# postmap /etc/postfix/passwords
Двоичные копиии имеют то же имя, но с расширением .db. Права доступа к оригинальному файлу полностью переносятся и на его двоичную копию.

Осталось проверить правильность работы системы. Попробуем отправить тестовое письмо от имени пользователя root пользователю root:
# mail -s test root
test
.
На ящик recipient@domain.tld должно прийти письмо от ящика sender@domain.tld.

воскресенье, 1 июня 2014 г.

Памятка по настройке Xen

Xen - довольно распространённая и хорошо зарекомендовавшая себя система виртуализации. В ней можно выделить три главных компонента:
  • Гипервизор - небольшое ядро, осуществляющее управление памятью и процессором,
  • dom0 - система, находящаяся под управлением гипервизора, но определяющая политику распределения памяти и процессоров, которую и реализует гипервизор,
  • domU - система, находящаяся под управлением гипервизора.
dom0, как и гипервизор, в системе может быть только один. domU может быть несколько. Для простоты в статье будем называть dom0 хост-системой, а domU - гостевыми системами, хотя строго говоря - они оба скорее гости, просто один из них имеет право управлять гипервизором.

Сервисы Xen, работающие в dom0 и осуществляющие управление гипервизором, написаны на Python. Соответственно, файлы конфигурации являются фактически исходными текстами на языке Python.

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

1. Настройка гипервизора

Устанавливаем пакет xen-linux-system:
# apt-get install xen-linux-system
Выставляем приоритет загрузки гипервизора Xen из GRUB:
# dpkg-divert --divert /etc/grub.d/08_linux_xen --rename /etc/grub.d/20_linux_xen
Для отмены приоритета загрузки Xen из GRUB можно выполнить следующую команду:
# dpkg-divert --rename --remove /etc/grub.d/20_linux_xen
Чтобы настройки GRUB вступили в силу, запустим обновление конфигурации GRUB:
# update-grub
2. Настройка хост-системы

Образы гостевых систем будем размещать на логических томах LVM2, поэтому сначала установим утилиты для управления логическими томами:
# apt-get install lvm2
Создадим новый физический том на специально выделенном для этого разделе:
# vgcreate vg0 /dev/sda3
Установим инструменты для создания образов и управления гостевыми системами:
# apt-get install xen-tools
Пропишем в файле /etc/xen-tools/xen-tools.cfg опции, задающие настройки создаваемой по умолчанию виртуальной машины:
# Использовать для образов гостевых систем физический том VG0
lvm = vg0

# Использовать debootstrap для установки системы на гостевой том
install-method = debootstrap

# Выделить для вновь создаваемой гостевой системы 10 гигабайт на логическом томе
size = 10Gb

# По умолчанию создавать гостевую систему, которой выделено 2 гигибайта оперативной памяти
memory = 2Gb

# По умолчанию создавать гостевую систему, которой выделен 1 гигабайт раздела подкачки
#noswap = 0
swap = 1Gb

# По умолчанию использовать файловую систему ext4
fs = ext4

# Настройки монтирования раздела ext4
ext4_options = noatime,nodiratime,errors=remount-ro

# Настройки задают ветку дистрибутива, версию ядра и загрузочного образа
dist = `xt-guess-suite-and-mirror --suite`
image = sparse
kernel = /boot/vmlinuz-`uname -r`
initrd = /boot/initrd.img-`uname -r`
pygrub = 1
mirror = `xt-guess-suite-and-mirror --mirror`
Все гостевые системы будут подключаться к виртуальному сетевому мосту, соединённому с физической сетевой картой. На самом деле сетевой мост является по сути виртуальным коммутатором, поддерживающим протокол STP, но по сложившейся традиции называется сетевым мостом. Установим утилиты для управления сетевым мостом:
# apt-get install bridge-utils
Настроим интерфейс xenbr0, подсоединив его к физическому сетевому интерфейсу eth0. Для этого впишем в файл /etc/network/interfaces следующие настройки:
auto xenbr0
iface xenbr0 inet static
  address 10.0.0.1
  netmask 255.255.255.0
  bridge_ports eth0
  bridge_maxwait 0
3. Создание образа гостевой системы

Для создания образа новой гостевой системы с именем ns, настройками по умолчанию, но с двумя выделенными системе ядрами процессора, введём на хост-системе следующую команду:
# xen-create-image --hostname ns --ip 10.0.0.2 --netmask 255.255.255.0 --gateway 10.0.0.1 --vcpus 2 --dist wheezy
После создания образа будет выведен пароль пользователя root в только что созданной системе. Его необходимо запомнить или записать и поменять при первом запуске созданного образа.

Теперь можно отредактировать конфигурацию только что созданной гостевой машины /etc/xen/ns.cfg:
vif = [ 'script=vif-bridge, bridge=xenbr0' ]
При запуске гостевой машины её первый интерфейс будет подключен к мосту хост-машины xenbr0. Если необходимо создать несколько сетевых интерфейсов, можно перечислить их внутри квадратных скобок через запятую.

4. Управление гостевыми системами

Запуск гостевой машины:
# xm create /etc/xen/ns.cfg
Просмотр списка активных гостевых машин:
# xm list
Просмотра потребления ресурсов внутри каждой гостевой машины:
# xm top
Остановка гостевой машины:
# xm shutdown ns
Вход на консоль гостевой машины:
# xm console ns
Для отключения от консоли можно нажать Ctrl и ] К сожалению, эта же комбинация клавиш используется в telnet, поэтому я предпочитаю сразу настроить сеть и SSH, а дальнейшую настройку гостевой системы осуществлять уже через SSH.

5. Донастройка гостевых систем

Файл конфигурации гостевой системы может выглядеть примерно следующим образом:
# Загрузчик pygrub позволяет передавать в ядро настройки,
# подобно тому как это делается при использовании GRUB
bootloader = '/usr/lib/xen-4.1/bin/pygrub'

# Количество процессорных ядер и объём памяти, выделяемый гостевой системе
vcpus       = '2'
memory      = '2048'

root        = '/dev/xvda2 ro'
disk        = [ 'phy:/dev/vg0/ns-disk,xvda2,w', # Образ диска находится на томе LVM2
                'phy:/dev/sda3,xvda3,w' ]       # Том хост-системы монтируется внутри гостевой системы

# Имя гостевой системы
name        = 'ns'

# На гостевой системе будут созданы интерфейсы eth0 и eth1, подключенные
# соответственно к сетевым мостам хост-системы xenbr0 и xenbr1
vif         = [ 'script=vif-bridge, bridge=xenbr0',
                'script=vif-bridge, bridge=xenbr1' ]

# Что делать с гостевой системой, если она решила выключиться или перезагрузиться
on_poweroff = 'destroy'
on_reboot   = 'restart'

# Что делать с гостевой системой, если произошла ошибка на хост-системе
on_crash    = 'restart'

# Отдельно задаём поведение гостевых систем при перезапуске сервиса управления
on_xend_start = 'ignore'
on_xend_stop = 'ignore'
6. Что следует установить в гостевую систему?

Я для себя сформировал следующий контрольный список:
# apt-get install vim less apt-file resolvconf binutils screen rsync
# apt-get install sysstat tcpdump file dnsutils telnet psmisc 
# apt-get install openntpd sudo postfix bsd-mailx logwatch
Не всё из этого необходимо, поэтому вы можете оставить из этого списка то, что вам нужно и дополнить его пакетами по своему вкусу. При установке openntpd и postfix стоит подумать об их настройке.

Если вам когда-нибудь понадобится воспользоваться консолью гостевой системы, стоит установить пакеты, ответственные за консоль, клавиатуру и локаль:
# apt-get install console-setup keyboard-configuration
Соответственно, можно задать их настройки при помощи следующих команд:
# dpkg-reconfigure console-setup
# dpkg-reconfigure keyboard-configuration
# dpkg-reconfigure locales
Ниже приведены пункты конфигурирования, отличающиеся от значений по умолчанию, которые я обычно выбираю для настройки этих пакетов:
Keyboard layout: Other
Country of origin for the keyboard: Russian
Keyboard layout: Russian

Encoding to use on the console: UTF-8
Character set to support: Cyrillic - KOI8-R and KOI8-U

Default locale for the systemm environment: ru_RU.UTF-8
7. Отключение сохранения-восстановления гостевых систем

При остановке демона xen, а также при выключении и перезагрузке системы, демон по умолчанию пытается сохранить состояние всех гостевых систем в каталог /var/lib/xend/storage, из-за чего может закончиться всё место на диске.

Изменить это поведение можно в файле /etc/default/xendomains настройкой значения XENDOMAINS_SAVE. Можно указать другой каталог, в котором есть достаточно места для сохранения образов оперативной памяти гостевых систем, а можно полностью отключить сохранение, указав пустое значение:
XENDOMAINS_SAVE=
Чтобы удалить сохранённые образы памяти гостевых систем, можно воспользоваться следующими командами:
# cd /var/lib/xen
# find . -type f -delete
8. Автозапуск виртуальных машин

По умолчанию при перезагрузке хост-системы восстанавливается сохранённое состояние гостевых систем. Если мы отключили сохранение и восстановление гостевых систем, то гостевые системы запущены не будут. Чтобы гостевые системы автоматически загружались при загрузке хост-системы, нужно создать каталог автозапуска и поместить внутрь него символические ссылки на конфигурации необходимых гостевых систем:
# mkdir /etc/xen/auto
# cd /etc/xen/auto
# ln -s /etc/xen/ns.cfg .
9. Решение проблем

9.1. Ошибка настройки сети

Гостевая система долго запускается, а затем выдаёт ошибку настройки сети:
"Error: Device 0 (vif) could not be connected. Could not find bridge, and none was specified".
В этом случае нужно просто отключить скрипт настройки сети, который запускается для всех гостевых систем. Для этого нужно закомментировать строку в файле /etc/xen/xend-config.sxp на хост-системе:
#(vif-script vif-bridge)
9.2. Ошибка запуска xen

Если команда xm list выдаёт ошибку:
Error: Unable to connect to xend: No such file or directory. Is xend running?
При запуске демонов xen при помощи команды /etc/init.d/xen start выводится ошибка:
[FAIL] Starting Xen daemons: xenstored xenconsoled xend failed!
И в журнале /var/log/xen/xend.log имеется запись вида:
[2013-12-06 09:21:27 4789] ERROR (SrvDaemon:349) Exception starting xend (unclosed token: line 1282, column 32)
Значит повреждено содержимое хранилища xen в каталоге /var/lib/xend и для запуска демонов xen его необходимо очистить при помощи команд:
# cd /var/lib/xend
# find . -type f -delete

воскресенье, 25 мая 2014 г.

Exim и Dovecot без SQL

Среди системных администраторов и инженеров распространено мнение, что чем система проще, тем она надёжнее. Конечно, не стоит воспринимать это утверждение буквально. Но, как говорится, умному - достаточно. Чем меньше компонентов в системе, чем проще взаимосвязи между компонентами, тем меньше вероятность того, что она сломается и тем выше вероятность того, что её будет просто починить.

Именно поэтому большинство системных администраторов Unix любят использовать текстовые файлы, которые легко редактируются при помощи любого текстового редактора (файлы XML к таковым обычно не относятся). Здесь я опишу настройку почтовой системы на основе Dovecot и Exim без использования базы данных. Вместо этого все данные будут помещаться в текстовых файлах.

Описание настройки основано на моих предыдущих заметках Установка и настройка Dovecot и Настройка Exim, практически повторяя их. Прошу прощения за самоплагиат - я считаю, что так будет удобнее.

1. Dovecot

Начнём с настройки Dovecot, поскольку для настройки Exim необходимо иметь уже настроенный Dovecot.

1.1. Установка Dovecot

Установим пакеты Dovecot, содержащие поддержку серверов POP3 и IMAP:
# apt-get install dovecot-core dovecot-imapd dovecot-pop3d
1.2. Подготовка системы

Создадим группу и пользователя vmail, от имени которого будет работать Dovecot и дадим этому пользователю доступ к каталогу, в котором будет храниться почта пользователей почтовой системы. На серверах для размещения почтовых ящиков, как и другой часто меняющейся информации, обычно используется раздел /var, который заранее делается достаточно большим. На настраиваемой мной системе больше всего свободного места на разделе home, поэтому я размещу почтовые ящики пользователей на нём:
# groupadd -g 120 -r vmail
# useradd -g 120 -r -u 120 vmail
# mkdir /home/vmail
# chown vmail:vmail /home/vmail
# chmod u=rwx,g=rx,o= /home/vmail
1.3. Базовая настройка Dovecot

Я буду использовать защищённые версии протоколов IMAP и POP3, поэтому настрою в файле /etc/dovecot/conf.d/10-auth.conf механизмы PLAIN и LOGIN, чтобы хранить пароли в базе данных в хэшированном виде:
disable_plaintext_auth = no
auth_default_realm = domain.tld
auth_mechanisms = plain login
!include auth-passwdfile.conf.ext
Настроим использование учётных данных из файла, подобного /etc/passwd, прописав в файле /etc/dovecot/conf.d/auth-passwdfile.conf.ext следующие секции:
passdb {
  driver = passwd-file
  args = scheme=CRYPT username_format=%u /etc/dovecot/passwd
}

userdb {
  driver = passwd-file
  args = username_format=%u /etc/dovecot/passwd

  # Поля по умолчанию, которые могут быть заменены значениями из файла passwd
  default_fields = uid=vmail gid=vmail userdb_home=/home/vmail/%Ld/%Ln userdb_location=maildir:/home/vmail/%Ld/%Ln userdb_quota_rule=*:storage=1G 

  # Поля, значения которых заменяют значения из файла passwd
  #override_fields = home=/home/vmail/%Ld/%Ln
}
Создадим в каталоге /etc/dovecot файл passwd и проставим права доступа:
# cd /etc/dovecot
# touch passwd
# chown root:dovecot passwd
# chmod u=rw,g=r,o= passwd
В файле /etc/dovecot/passwd могут быть следующие поля:
user:{plain}password:uid:gid:gecos:home:shell:extra_fields
Назначение полей:
  • user - почтовый ящик (в данном случае - вместе с доменом),
  • password - пароль (можно явным образом указывать алгоритм хэширования пароля),
  • uid - системный идентификатор владельца файлов почты,
  • gid - системный идентификатор группы владельца файлов почты,
  • gecos - справочная информация о почтовом ящике (игнорируется),
  • home - путь к каталогу почты,
  • shell - интерпретатор (игнорируется),
  • extra_fields - дополнительные настройки (квота, например).
Любое из полей может быть не определено в файле, если в настройках Dovecot указаны значения этих полей по умолчанию. При указании дополнительных полей, используемых в секции userdb необходимо перед именем поля указывать префикс «userdb_», как в примере выше, в настройках default_fields. Имеется возможность зафиксировать часть настроек почтового ящика при помощи настройки override_fields, так что значения из файла будут игнорироваться.

Подробнее о формате файла и других настройках можно прочитать на официальной wiki-странице Dovecot: Passwd-file

Изменим форматирование отметок времени, вписав в файл /etc/dovecot/conf.d/10-logging.conf следующую настройку:
log_timestamp = "%Y-%m-%d %H:%M:%S "
На время отладки также можно включить другие опции из этого файла:
auth_verbose = yes
auth_verbose_passwords = yes
auth_debug = yes
mail_debug = yes
В файле /etc/dovecot/conf.d/10-mail.conf настроим путь к почтовым ящикам и пользователя, от имени которого dovecot будет работать с ящиками:
mail_home = /home/vmail/%Ld/%Ln
mail_location = maildir:/home/vmail/%Ld/%Ln
mail_uid = vmail
mail_gid = vmail
first_valid_uid = 120
last_valid_uid = 120
first_valid_gid = 120
last_valid_gid = 120
Сейчас настроим сервис, при помощи которого Exim будет проверять учётные данные почтовых клиентов. Для этого отредактируем файл /etc/dovecot/conf.d/10-master.conf и впишем в него настройки сервиса:
service auth {
  unix_listener auth-client {
    mode = 0660
    user = Debian-exim
    #group = 
  }
}
Зададим в файле /etc/dovecot/conf.d/15-lda.conf адрес, с которого Dovecot будет отправлять сообщения об ошибках:
postmaster_address = postmaster@domain.tld
Осталось отредактировать файл /etc/dovecot/dovecot.conf, указав в нём адрес, на котором сервер будет ожидать подключений:
!include_try /usr/share/dovecot/protocols.d/*.protocol
listen = *
!include conf.d/*.conf
!include_try local.conf
Начальная настройка сервера окончена. Осталось перезапустить Dovecot, чтобы настройки вступили в силу:
# /etc/init.d/dovecot restart
1.4. Настройка плагина acl

Плагин acl позволяет пользователям предоставлять друг другу доступ к папкам в своих почтовых ящиках. Это может быть полезно для корпоративных пользователей. Например, для директора и его заместителя. Или для директора и его секретаря. Или для сотрудников из одного отдела, которые подменяют друг друга на время обеда или отпуска. Эта возможность, естественно, доступна только при использовании протокола IMAP.

В файле /etc/dovecot/conf.d/10-mail.conf включаем использование плагина:
mail_plugins = acl
В файле /etc/dovecot/conf.d/20-imap.conf включаем использование плагина в IMAP-сервере:
protocol imap {
  mail_plugins = $mail_plugins imap_acl
}
В файле /etc/dovecot/conf.d/10-mail.conf прописываем следующие настройки:
namespace inbox {
  type = private
  separator = /
  prefix =
  inbox = yes
}

namespace {
  type = shared
  separator = /
  prefix = shared/%%u/
  location = maildir:%%h:INDEX=%h/shared/%%u
  subscriptions = yes
  list = children
}
Эти настройки описывают два пространства имён: в первом хранится личная почта пользователя, а во втором будут отображаться каталоги других пользователей, к которым этот пользователь имеет доступ.

Поясню смысл настроек location для пространства имён общих каталогов:
  • maildir:%%h - означает место расположения чужого почтового ящика в формате Maildir,
  • %%h - полный путь к Maildir-каталогу чужого ящика,
  • INDEX=%h/shared/%%u - задаёт каталог, в который как бы монтируются каталоги чужой почты, к которым её владелец дал нам доступ,
  • %h - путь к Maildir-каталогу нашего ящика,
  • %%u - имя другого пользователя в виде box@domain.tld.
В файл /etc/dovecot/conf.d/90-acl.conf прописываем настройки плагина:
plugin {
  acl = vfile
  acl_shared_dict = file:/home/vmail/%Ld/shared-mailboxes.db
}
Значение vfile предписывает создавать внутри почтового ящика файл dovecot-acl, в котором и будут прописываться права доступа к нему со стороны других пользователей.

Значение acl_shared_dict указывает путь к файлу словаря, который позволит пользователям узнавать, к каким каталогам в чужих почтовых ящиках у них имеется доступ. В данном случае для каждого домена будет создан отдельный файл словаря, расположенный в каталоге домена, на одном уровне с ящиками.

Заодно опишем в файле /etc/dovecot/conf.d/15-mailboxes.conf назначение различных каталогов внутри пространства имён, в котором хранится личная почта пользователя:
namespace inbox {
  mailbox Drafts {
    special_use = \Drafts
  }
  mailbox Junk {
    special_use = \Junk
  }
  mailbox Trash {
    special_use = \Trash
  }
  mailbox Sent {
    special_use = \Sent
  }
}
Назначение каталогов:
  • Drafts - каталог черновиков,
  • Junk - каталог для спама,
  • Trash - каталог для удалённых писем,
  • Sent - каталог для отправленных писем.
Современные почтовые программы смогут прямо по протоколу IMAP узнать назначение каждого из специальных каталогов, вне зависимости от их названия. Это бывает полезно, если каталог имеет нестандартное название или название на языке пользователя ящика, например "Входящие" или "Спам".

Чтобы настройки плагина acl вступили в силу, нужно перезапустить Dovecot:
# /etc/init.d/dovecot restart
1.5. Настройка плагина quota

Плагин quota позволяет назначить для почтового ящика ограничения на объём хранящихся в нём писем или даже на их общее количество. На мой взгляд, ограничение на общее количество писем имеет довольно мало смысла. Единственная польза, которая мне приходит на ум - это возможность защититься от исчерпания inode'ов в файловой системе, если кто-то намеренно решит отправить огромное количество мелких писем в ящики пользователей, с целью нарушить работу почтовой системы.

Мы настроим плагин так, чтобы он использовал значения квот, указанные в интерфейсе Postfixadmin. Эти квоты ограничивают только максимальный объём писем в ящике.

Включим использование плагина в файле /etc/dovecot/conf.d/10-mail.conf:
mail_plugins = acl quota
Жирным шрифтом показан добавленный текст, а курсивом - текст, добавленный нами при включении плагина acl. Если вы не включали плагин acl, то вписывать этот текст не нужно.

В файл /etc/dovecot/conf.d/15-lda.conf впишем, что в случае превышения квоты Dovecot должен сообщать о временной ошибке, но не отклонять письмо окончательно. Почтовый сервер отправителя (или наш MTA) будет периодически предпринимать повторные попытки в надежде на то, что адресат почистит свой ящик от ненужных писем.
quota_full_tempfail = yes
В файл /etc/dovecot/conf.d/20-imap.conf добавим поддержку квот в IMAP-сервере:
protocol imap {
  mail_plugins = $mail_plugins imap_acl imap_quota
}
Этот плагин позволит почтовым клиентам, работающим по протоколу IMAP, узнавать квоту почтового ящика и её текущее использование.

Укажем в файле /etc/dovecot/conf.d/90-quota.conf, что значения квот берутся из словаря и зададим пустое правило по умолчанию:
plugin {
  quota = dict:user::file:%h/dovecot-quota
  quota_rule = *:
}
Осталось перезапустить Dovecot, чтобы настроенный плагин начал работать:
# /etc/init.d/dovecot restart
В каталоге каждого почтового ящика будет создаваться файл dovecot-data, внутри которого будет вестись учёт текущего количества сообщений в ящике и их объёма. Чтобы принудительно пересчитать квоты всех почтовых ящиков, можно воспользоваться следующей командой:
# doveadm quota recalc -A
1.6. Настройка плагина expire

Этот плагин не поддерживает работу со словарями, хранящимися не в базах данных, поэтому здесь его настройка не описывается.

Для периодической очистки почтовых ящиков от устаревших сообщений можно добавить в планировщик задач выполнение, например, такой команды:
doveadm expunge -A mailbox Spam savedbefore 2w
Эта команда найдёт в почтовых ящиках пользователей каталоги Spam и удалит из них те сообщения, которые были сохранены в ящик более двух недель назад. Подобным образом можно очищать и другие каталоги, например - Trash.

1.7. Настройка SSL

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

Если у вас имеются готовый подписанный сертификат, можно включить поддержку SSL в файле /etc/dovecot/conf.d/10-ssl.conf и указать в нём пути к файлам сертификата:
ssl = yes
ssl_cert = </etc/ssl/mail_public.pem
ssl_key = </etc/ssl/mail_private.pem
После настройки сертификатов нужно перезапустить Dovecot, чтобы изменения вступили в силу:
# /etc/init.d/dovecot restart
1.8. Настройка плагина sieve

Sieve - это скрипты фильтрации почты, которые выполняются агентом локальной доставки (LDA) в момент получения письма от почтового сервера (MTA). Скрипты позволяют раскладывать письма в разные папки, ориентируясь на их содержимое - тему письма, получателей, отправителей и т.п. Можно удалить письмо, переслать его на другой ящик или отправить уведомление отправителю, причём использовать можно любое поле заголовка или содержимое тела письма.

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

Конечно, в наши времена больших почтовых сервисов типа Gmail или Яндекс-почты, этим никого не удивишь. Но тут плюс заключается в том, что перед нами не стоит дилемма "удобство" - "безопасность". Мы можем хранить почту у себя, не делясь ею с посторонними компаниями, имея над ней полный контроль, и в то же время можем пользоваться удобствами, характерными для больших почтовых сервисов.

Установим пакет с плагином Sieve:
# apt-get install dovecot-sieve
Включим использование плагина в файле /etc/dovecot/conf.d/15-lda.conf:
protocol lda {
  mail_plugins = $mail_plugins sieve
}
Укажем настройки плагина в файле /etc/dovecot/conf.d/90-sieve.conf:
plugin {
  sieve = /home/vmail/%Ld/%n/active.sieve # Расположение активного скрипта
  sieve_dir = /home/vmail/%Ld/%n/sieve    # Каталог для скриптов
  sieve_max_script_size = 1M              # Максимальный размер одного скрипта
  sieve_quota_max_scripts = 50            # Максимальное количество скриптов
  sieve_quota_max_storage = 1M            # Максимальный общий объём скриптов
}
Каждый пользователь может обладать собственным набором Sieve-скриптов, из которых в любой момент времени активным может быть только один. Каталог для скриптов указывается в настройке sieve_dir, а в настройке sieve указывается имя символической ссылки, которая будет указывать на активный скрипт.

После настройки плагина нужно перезапустить Dovecot, чтобы изменения вступили в силу:
# /etc/init.d/dovecot restart
Подробнее о скриптах Sieve можно почитать на Википедии, в статье Sieve.

1.9. Настройка сервиса managesieve

Плагин sieve не был бы столь полезным, если бы Sieve-скриптами нельзя было бы управлять прямо из почтового клиента. Именно эту функцию и реализует сервис ManageSieve. Он ожидает подключений клиентов на отдельном TCP-порту 4190. Для управления скриптами клиент использует учётные данные своего почтового ящика.

Для включения сервиса достаточно лишь установить дополнительный пакет:
# apt-get install dovecot-managesieve
В следующих заметках фильтрация писем при помощи Sieve будет рассмотрена подробнее - я покажу, как им пользоваться в почтовых клиентах.

1.10. Результирующий файл конфигурации

Поскольку настроек очень много, проверить их можно при помощи следующей команды:
$ doveconf -n
Опция n предписывает показывать только те настройки, которые отличаются от настроек по умолчанию. У меня со всеми плагинами, настройка которых была тут описана, команда выдаёт следующий результат:
# 2.1.7: /etc/dovecot/dovecot.conf
# OS: Linux 3.2.0-4-amd64 x86_64 Debian 7.5 ext4
auth_default_realm = domain.tld
auth_mechanisms = plain login
disable_plaintext_auth = no
first_valid_gid = 120
first_valid_uid = 120
last_valid_gid = 120
last_valid_uid = 120
listen = *
log_timestamp = "%Y-%m-%d %H:%M:%S "
mail_gid = vmail
mail_home = /home/vmail/%Ld/%Ln
mail_location = maildir:/home/vmail/%Ld/%Ln
mail_plugins = quota acl
mail_uid = vmail
managesieve_notify_capability = mailto
managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date ihave
namespace {
  list = children
  location = maildir:%%h:INDEX=%h/shared/%%u
  prefix = shared/%%u/
  separator = /
  subscriptions = yes
  type = shared
}
namespace inbox {
  inbox = yes
  location = 
  mailbox Drafts {
    special_use = \Drafts
  }
  mailbox Junk {
    special_use = \Junk
  }
  mailbox Sent {
    special_use = \Sent
  }
  mailbox Trash {
    special_use = \Trash
  }
  prefix = 
  separator = /
  type = private
}
passdb {
  args = scheme=CRYPT username_format=%u /etc/dovecot/passwd
  driver = passwd-file
}
plugin {
  acl = vfile
  acl_shared_dict = file:/home/vmail/%Ld/shared-mailboxes.db
  quota = dict:user::file:%h/dovecot-quota
  quota_rule = *:
  sieve = /home/vmail/%Ld/%Ln/active.sieve
  sieve_dir = /home/vmail/%Ld/%Ln/sieve
  sieve_max_script_size = 1M
  sieve_quota_max_scripts = 50
  sieve_quota_max_storage = 1M
}
postmaster_address = postmaster@domain.tld
protocols = " imap sieve pop3"
service auth {
  unix_listener auth-client {
    group = Debian-exim
    mode = 0660
  }
}
ssl_cert = </etc/ssl/mail.domain.tld.public.pem
ssl_key = </etc/ssl/mail.domain.tld.private.pem
userdb {
  args = username_format=%u /etc/dovecot/passwd
  default_fields = uid=vmail gid=vmail userdb_home=/home/vmail/%Ld/%Ln userdb_location=maildir:/home/vmail/%Ld/%Ln userdb_quota_rule=*:storage=1G
  driver = passwd-file
}
protocol lda {
  mail_plugins = quota acl sieve
}
protocol imap {
  mail_plugins = quota acl imap_quota imap_acl
}
2. Настройка Exim

Настройка Exim, как уже было сказано, зависит от настроенного Dovecot. Exim использует сервис Dovecot для SMTP-аутентификации и список почтовых ящиков из файла /etc/dovecot/passwd.

2.1. Установка

Установим SMTP-сервер Exim:
# apt-get install exim4-daemon-heavy
Создаём файл конфигурации /etc/exim4/exim4.conf со следующим начальным содержимым:
# Имя нашей почтовой системы
primary_hostname = mail.domain.tld

# Список доменов нашей почтовой системы
domainlist local_domains = /etc/exim4/local_domains

# Список доменов, для которых наша почтовая система является резервной
domainlist relay_domains = /etc/exim4/relay_domains

# Список узлов, почту от которых будем принимать без проверок
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}}

  # Пересылки для получателей в разных доменах
  aliases:
    driver = redirect
    allow_fail
    allow_defer
    data = ${lookup{$local_part@$domain}lsearch{/etc/exim4/aliases}}  

  # Получение почты на локальный ящик
  mailbox:
    driver = accept
    condition = ${lookup{$local_part@$domain}lsearch{/etc/dovecot/passwd}{yes}{no}}
    user = dovecot
    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= /ect/exim4/exim4.conf
# chown root:Debian-exim /etc/exim4/exim4.conf
В этом случае можно дать остальным пользователям доступ на чтение, т.к. никакой особо секретной информации в файле конфигурации нет. С другой стороны - нужды давать такой доступ тоже нет.

Чтобы Exim мог читать файл /etc/dovecot/passwd, включим пользователя Debian-exim в группу dovecot:
# usermod -aG dovecot Debian-exim
Осталось запустить Exim, чтобы он начал работать в минимальной конфигурации:
# /etc/init.d/exim4 start
2.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)

2.3. Проверка квот

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

Можно было бы написать небольшой скрипт для проверки квот, но делать этого я не стал. Если вы считаете, что без проверки квот на этапе RCPT протокола ESMTP обойтись ну никак нельзя, попробуйте написать этот скрипт самостоятельно. Я не думаю, что он займёт больше пары десятков строчек. Не забудьте только, что стоит использовать действие discard вместо deny, чтобы в случае нескольких получателей письмо было доставлено в те ящики, квота которых не превышена.

2.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
2.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
2.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
2.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.

Включим Debian-exim в группу greylist, чтобы Exim имел доступ к сокету и файлам greylistd:
# usermod -aG greylist Debian-exim
Осталось попросить Exim перезагрузить файл конфигурации, чтобы новые настройки вступили в силу:
# /etc/init.d/exim4 reload
2.8. Настройка SPF-записи

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

2.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
Установим библиотеку и заголовочные файлы:
# apt-get intall libspf2-2 libspf2-dev
Собираем пакет:
# 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
2.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
2.11. Итоговый файл конфигурации

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

# Список доменов нашей почтовой системы
domainlist local_domains = /etc/exim4/local_domains

# Список доменов, для которых наша почтовая система является резервной
domainlist relay_domains = /etc/exim4/relay_domains

# Список узлов, почту от которых будем принимать без проверок
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

    # Если отправитель попал в чёрный список, отправляем его в грейлистинг
    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}}

    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

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

  # Пересылки для получателей в разных доменах
  aliases:
    driver = redirect
    allow_fail
    allow_defer
    data = ${lookup{$local_part@$domain}lsearch{/etc/exim4/aliases}}
  
  # Получение почты на локальный ящик
  mailbox:
    driver = accept
    condition = ${lookup{$local_part@$domain}lsearch{/etc/dovecot/passwd}{yes}{no}}
    user = dovecot
    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
Заключение

В описанном варианте настройки мне не нравится то, что файлы с настройками не удалось передать в собственность отдельной группы пользователей. Это позволило бы предоставлять определённым системным пользователям доступ на редактирование этих файлов. Кроме того, можно было бы написать даже веб-приложение для управления доменами, почтовыми ящиками и пересылками. Не удалось это сделать по той простой причине, что Dovecot наотрез отказывается использовать вторичные группы при доступе к файлам. Возможно это недоработка самого Dovecot, а может быть - предусмотренное поведение. Сейчас у меня нет острой необходимости разобраться в этом, поэтому я оставил всё как получилось: пользователь Debian-exim состоит в группах dovecot и greylist, а в группе Debian-exim состоит пользователь clamav.

Для проверки квот на этапе RCPT сеанса ESMTP можно было бы воспользоваться не только самописным скриптом, но и сервисом проверки квот quota-status, который появился в Dovecot 2.2 (в Debian Wheezy поставляется Dovecot версии 2.1.7), благо Exim позволяет отправлять запросы в юникс-сокеты и читать из них ответ.

Как обычно, готов выслушать полезные замечания, предложения, советы.