От автора

Конфигурирование любого MTA - занятие весьма непростое. Сам по себе протокол SMTP дырявый, а значит проблема в корне: невозможно создать простой в конфигурировании MTA, и нужно это принять как его неотъемлемую часть. Поэтому сразу же готовимся к тому, что настройка не будет легкой. Также обратите внимание, что статья расчитана на небольшие почтовые сервера с таким числом почтовых ящиков, которые нетрудно администрировать вручную (в общем случае не больше 40-50).

В статье приводятся ссылки на различные части RFC протокола SMTP, в скобках приводится конкретный пункт. Полный текст сего документа можно посмотреть на IETF под номером 2821.

Организация почтовой базы данных

Здесь я понимаю под словом "база данных" именно хранилище учетных данных пользователей (почтовых ящиков). Я не рекомендую использовать реляционную СУБД (MySQL, PostgreSQL) на небольшом количестве ящиков. Реляционная СУБД - это всегда тяжелое приложение, которое кушает память и терроризирует дисковую подсистему. Каждый отдельный процесс exim создает отдельное подключение к БД, что на первых порах (когда этих процессов работает не больше 5-6 одновременно) никак заметно не сказывается на работе системы в целом; но когда поток почты становится интенсивнее, сервер начинает заметно тормозить, требуя тонкого тюнинга СУБД. Этот тюнинг однозначно нужен при высокой нагрузке, и всегда требует правильной организации самих данных в базе. На маленьком сервере это просто абсурд, а на большом - производственная необходимость. Лучше начать с простой базы данных в /etc/passwd или CDB, чтобы потом была возможность спроектировать реляционную БД с нуля.

В данной статье я описываю настройку exim на хранение учетных данных в /etc/passwd. Также, я предлагаю при нескольких почтовых доменах выбрать один основной, а остальные (которые в этом случае будут виртуальными) маршрутизировать на основной с помощью алиасов. Например, наш основной домен - mydomain.ru, в котором есть ящики info@mydomain.ru и sales@mydomain.ru. Теперь требуется сделать домен workdom.ru с ящиками info@workdom.ru и admin@workdom.ru. Поскольку домены хранить в /etc/passwd нереально (на группу и пользователя там ограничение в 16 символов), то пусть адрес info@workdom.ru будет ссылкой на info.wd.ru@mydomain.ru, а admin@workdom.ru соответственно ссылкой на admin.wd.ru@mydomain.ru. Это абсолютно нормальный рабочий способ организации виртуального почтового хостинга для небольшого числа вручную администрируемых ящиков. Конечно, при большом количестве ящиков полюбому придется сварганить что-то универсальное, но эти ситуации уже выходят за пределы предметной области данной статьи.

Установка

Ставим из портов:

Не забудем включить "автозагрузку" этих сервисов в /etc/rc.conf:

exim_enable=yes
tpop3d_enable=yes
clamav_clamd_enable=yes
clamav_freshclam_enable=yes

Также не забудем отключить процессы sendmail, обслуживающие входящие соединения. В /etc/rc.conf:

sendmail_enable=no
sendmail_submit_enable=no

Перезапуск sendmail:

cd /etc/mail
make stop
make start

Если мы вроде бы отправляем почту, а в журнале exim ничего нет, контролируем что exim прослушивает 25 порт на всех интерфейсах:

# sockstat |grep 25
mailnull exim-4.69- 800 4 tcp4 *:25 *:*

Конфигурирование

Основной конфиг exim находится здесь: /usr/local/etc/exim/configure. На первый взгляд, он покажется излишне сложным! Однако, когда наступит тот самый момент (просят сделать что-то очень специфичное), под этой сложностью обнаружится гибкость.

Глобальные опции

Итак, primary hostname должен указывать не на домен конторы, а на доменное имя сервера. Нечто вроде mail.mydomain.ru. Не поленимся создать отдельное имя для сервера. Я глубоко уважаю тех людей, которые не ленятся использовать свое воображение, придумывая осмысленные имена серверам (например, mercury для сервера электронной почты).

Список domainlist local_domains содержит в себе список почтовых доменов, которые должен обслуживать этот сервер. Здесь необходимо четко представлять себе, что такое MX-запись в домене, и для чего она нужна (как работает). Собака "@", которая здесь указана по умолчанию, заменяется тем, что указано в primary_hostname - я ее всегда убираю и указываю все домены непосредственно руками, однако доменное имя сервера все же следует включить в этот список. Все элементы в таких списках разделяются символом двоеточия ":". Например:

domainlist local_domains = mercury.mydomain.ru : mydomain.ru : corp.mydomain.ru

Список hostlist relay_from_hosts перечисляет DNS-имена и IP-адреса всех, кто имеет право использовать этот сервер для исходящего транзита (SMTP-relay). Следует понять, что разрешать релей в общем случае лучше на основании статических (а не динамических) данных. SMTP-авторизация должна применяться разумно (а не так, как мы это обычно делаем). Пример списка:

hostlist relay_from_hosts = localhost : 192.168.1.0/24 : 10.0.0.0/16

Это список соответствия конкретной команды SMTP группе проверок ACL:

acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data

Подробнее о настройке ACL см. ниже.

Опция qualify_domain используется сервером, когда вместо полного email адреса есть лишь его локальная часть (без собаки и домена). Чтобы в алиасах можно было использовать короткие адреса, определим для них домен:
qualify_domain = mydomain.ru
qualify_recipient =

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

host_lookup =
rfc1413_hosts =

Журнал /var/log/exim/mainlog смотреть гораздо удобнее, если журнализация настроена:

log_selector = +delivery_size -host_lookup_failed +received_recipients +return_path_on_delivery -tls_cipher

Разумно ограничим максимальное число паралельных процессов exim:

smtp_accept_max = 70

Превышение этого предела одновременными соединениями приведет к отказу в обслуживании очередного входящего соединения (RST TCP). Такое поведение со стороны сервера-получателя приводит к временной задержке (defer) письма на сервере-отправителе, что является абсолютно нормальной ситуацией. Однако, в случаях разбора полетов, аля "кто виноват в задержке письма", лучше иметь подтверждение в чужом журнале, что сервер-получатель именно был слишком занят, а не тупо лежал. Для этого лучше ограничивать таким способом: принимаем входящее подключение, но сразу же "просим" зайти попозже корректным ответом SMTP 4xx:

acl_smtp_connect = acl_check_connect

а в секции ACL:

acl_check_connect:

    accept hosts = +relay_from_hosts

    defer message = Sorry, too busy. Try again later.
    ratelimit = 20 / 10s / $primary_hostname

    accept

Значения 20 / 10s в строке ratelimit означают, что за 10 секунд сервер будет обслуживать максимум 20 соединений. Остальные, получив временную ошибку, пойдут пробовать позднее. Конкретные цифры здесь следует регулировать опытным путем, поскольку все зависит от прожорливости (по отношению к ресурсам) отдельных процессов exim, а также от производительности сервера, конечно. Это рабочий способ, но он нарушает RFC (3.1) - если у Вас есть предложение получше, я рад увидеть (услышать) об этом.

Ограничим максимальное число паралельных SMTP-сессий с одного IP:

smtp_accept_max_per_host = 10

Разрешим использовать символ подчерка в HELO:

helo_allow_chars = _

Сделаем bounce сообщения, так или иначе связанные с нами, более читабельными и понятными:

smtp_return_error_details = yes

За что я не люблю телевизор - он не оставляет мне ни одного шанса изменить жизнь к лучшему. Когда кто-то бухтит, ни капли не интересуясь обратной реакцией, ему пора в тело петуха. Пусть петухи занимаются своей прямой обязанностью (будят людей по утрам), а в Интернете им делать нечего:

smtp_enforce_sync = yes

Согласно RFC (4.3.1), так называемый "баннер" должен содержать полное доменное имя сервера после кода 220:

smtp_banner = Mercury.MyDomain.RU ESMTP Service Ready

Ограничим число команд, которые сервер-отправитель может передать ДО команды "MAIL":

smtp_accept_max_nonmail = 7

Разрешаем серверу-отправителю ошибаться всего лишь один раз за сессию:

smtp_max_unknown_commands = 1

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

smtp_accept_queue_per_connection = 15

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

Наконец, укажем независимый контакт администратора сервера с помощью макроса (будем использовать его сами ниже):

ADMIN_CONTACT = bill at microsoft.com.

Этот контакт будет использоваться сервером в реджектах, которые с небольшой долей вероятности могут быть причиной "застревания" нормальной рабочей почты. Можно указать рабочий или сотовый телефон - конкретный способ контакта выбираем сообразно обстановке.

ACL

Все ACL-секции и правила используются в момент SMTP-диалога, т.е. если мы хотим ответить серверу-отправителю что-то специфичное ДО начала обработки письма, мы должны описать это именно в ACL. К счастью, эти списки обладают весьма обширными возможностями, описывать которые я не берусь - ознакомиться с ними можно здесь. Если сервер-отправитель прошел все проверки ACL, а сервер-получатель успешно принял тело письма и закрыл сессию, то далее ошибки обработки уже приведут к формированию bounce-сообщений.

ACL-секцией я назвал группу проверок, которая запускается после передачи сервером-отправителем команды конкретного определенного типа. ACL-секции в конфигурации можно называть как угодно, т.к. соответствие "команда SMTP - ACL-секция" устанавливается глобальными опциями конфигурации выше. В default-конфигурации эти соответствия выглядят следующим образом:

acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data

Это означает: после команды RCPT TO: запускается группа проверок ACL под именем "acl_check_rcpt", а сразу после передачи команды DATA и тела письма - "acl_check_data". Список всех возможных типов команд SMTP можно посмотреть здесь. Сами же ACL-секции указываются после служебного "указателя":

begin acl

Далее следуют заголовки ACL-секций в виде их имен с двоеточием на конце и, собственно, список проверок как содержимое каждой секции. Например:

acl_check_rcpt:

  accept hosts = :

  deny message = Restricted characters in address
      domains = +local_domains
      local_parts = ^[.] : ^.*[@%!/|]

...

Следует понять, что даже простое включение проверки какой либо SMTP-команды с помощью ACL заставляет Exim проверять синтаксис соответствующих данных. С этой точки зрения, пустая ACL-секция (с одной лишь командой accept) уже повышает степень "придирчивости". Например в случае с HELO, эта придирчивость практически выражается в невозможности использовать в HELO кириллицу. Стоит выключить проверку helo в глобальных опциях, как кириллица "хавается" без проблем. Чтобы оставить проверки HELO, но при этом добавить смирения Exim-у по отношению к русским буквам, пропишем в глобальных опциях сети (или хосты), которым это разрешено:

helo_accept_junk_hosts = 192.168.0.0/16

Подробнейшее описание каждой из ACL-секций default-конфигурации можно найти в документации по Exim (пункт 7.2). Я же опишу здесь, что им не хватает, и как это исправить.

HELO

Для начала, научим сервер проверять приветствие сервера-отправителя (команда HELO). Таким образом можно достаточно эффективно вычислять внешние (в Интернете) зомби-машины. Добавляем заголовок новой ACL-секции:

acl_check_helo:

"Своих" проверять не следует, т.к. в большинстве случаев это - MUA, которые отправляют в HELO свое сетевое имя (которое иногда бывает даже в кирилице):

    accept hosts = +relay_from_hosts

Согласно RFC (4.1.1.1), клиент передает в HELO свое полное доменное имя, или свой IP адрес в виде литерала. Поэтому смело отправляем подальше тех, кто представляется явно не своими именами, или вообще никак не представился:

    drop    message     = Bad HELO: I am the localhost! ;)
            condition   = ${if eq{localhost}{$sender_helo_name}}

    drop    message     = Bad HELO: Host impersonating [$sender_helo_name]
            condition   = ${if match{$sender_helo_name}{$primary_hostname}{yes}{no}}

    drop    message     = Bad HELO: Host impersonating [$sender_helo_name]
            condition   = ${if match_domain{$sender_helo_name}{+local_domains}{true}{false}}

    drop    message     = Bad HELO: empty. Required by RFC.
            condition   = ${if eq {$sender_helo_name}{}{yes}{no}}
Согласно RFC (4.1.3), литерал IP-адреса выглядит как IP-адрес в квадратных скобках. Забираем почту, если литерал содержит адрес сервера-отправителя:
    accept  condition   = \
                        ${if and \
                            { \
                                {match{$sender_helo_name}{^\\[\(\\d\{1,3\}.\\d\{1,3\}.\\d\{1,3\}.\\d\{1,3\}\)\\]\$}} \
                                {isip {$1} } \
                                {match_ip{$1}{$sender_host_address} } \
                            } \
                        {1}{0}}
Литералы с IP-адресом, не соответствующим адресу сервера-отправителя, также как и IP-адреса в чистом виде (без квадратных скобок) пойдут "лесом" на следующих проверках.

Очень часто зомби машины, рассылающие спам и вирусы, пихают в HELO свое DNS-имя из обратной зоны. Можно этим воспользоваться, так как провайдеры зачастую делают эти зоны автоматически заполняемыми, в результате чего имена их говорят сами за себя. Например, это: dsl-12-73-191.someisp.com. Я сам вручную составил данный список слов, которые постоянно встречаются в HELO спама; советую Вам редактировать его сообразно обстановке, журнал Exim mainlog можно использовать в качестве источника.

    drop  message     = Bad HELO: Use mail server of your ISP or contact us by ADMIN_CONTACT.
          condition   = ${if match{$sender_helo_name}{\
                                dynamic|\
                                dsl|\
                                broadband|\
                                cable|\
                                ppp|\
                                user\\.veloxzone|\
                                pool|\
                                dialup|\
                                dialin|\
                                dhcp|\
                                \\.dyn\\.user\\.\
                        }{yes}{no}}
Однако следует сказать, что это достаточно строгие проверки, которые вероятно могут привести к ложным срабатываниям. Здесь пригодится контакт админа - он засветится в bounce-сообщениях и журнале сервера-отправителя.

Бывает так, что в HELO нет слова, явно указывающего на динамичность хоста, но зато есть масса цифр, которые говорят об этом - это раз. Доменное имя всегда представляет собой алфавитно-цифровые символы с тире и подчерками, разделенные на уровни с помощью точек; например, значения "8359e7a9bcf5429" и "system-11f4b201" не являются доменными именами - это два. Следующая проверка может отловить оба "раза": она отправит погулять тех, кто представляется такими "недоменными" именами, а также именами, содержащими в себе IP адрес (где точки возможно заменены на тире):

    drop  message     = Bad HELO: Use mail server of your ISP or contact us by ADMIN_CONTACT.
          condition   = ${if match{$sender_helo_name}{\
                                \\d\\d?\\d?\\.\\d\\d?\\d?\\.\\d\\d?\\d?|\
                                \\d\\d?\\d?-\\d\\d?\\d?-\\d\\d?\\d?|\
                                \^[\\d\\w-_]+\$\
                        }{yes}{no}}
Остальные значения скорее всего являются доменными именами, поэтому принимаем их:
    accept

На этом описание ACL-секции acl_check_helo завершено. Не забываем "прикрутить" имя этой секции к соответствующей команде SMTP среди глобальных опций exim (выше):

acl_smtp_helo = acl_check_helo

MAIL FROM

Далее, научим сервер проверять синтаксис email адреса-отправителя (команда MAIL FROM):

acl_check_sender:

  drop  message         = Restricted characters in address local part ($sender_address_local_part)
	condition       = ${if match{$sender_address_local_part}{^[|]}{yes}{no}}

  drop	message		= Automatically generated addresses are not welcome here.
	condition	= ${if match{$sender_address_local_part}{\%CUST_WORD}{yes}{no}}

  accept

Смысл данных проверок на данный момент лишь в том, чтобы посылать подальше тех, кто использует в адресе вертикальную черту "|". А также бажные скрипты автоматической рассылки, которые по неизвестной мне причине в упор не замечают шаблон %CUST_WORD вполне понятного назначения.

Также не забудем "прикрутить" имя этой секции к соответствующей команде SMTP:

acl_smtp_mail = acl_check_sender

RCPT TO

ACL-секция для этой команды в default-конфигурации уже присутствует, и она вполне работоспособона. Однако, я желаю прокоментировать отдельные ее части (и возможно, немного подправить их).

В этой секции в нескольких местах применяется механизм верификации адреса. Суть его проста: email-адрес прогоняется по роутерам так, как будто он используется для доставки; результат такой "маршрутизации" возвращается и применяется в ACL-проверке соответственно. С помощью опций, процесс проверки можно конфигурировать. Это очень полезный и мощный механизм, который однако стоит внимательно изучить, прежде чем писать (или править) проверки на его основе. В частности, опция callout должна применяться с умом, так как безответственность в этом непременно приведет к блокировке почтового-сервера некоторыми RBL-списками. Пример того, как не надо делать:

  require verify = sender/callout=5s

Если вы хотите использовать SMTP-авторизацию, то убедитесь в том, что вместе с ней используются какие-то дополнительные механизмы предотвращения несанкционированного использования вашего MTA в качестве open-relay сервера. Например, разумно разрешить использовать SMTP-авторизацию только для конкретных IP-сетей, где находятся ваши пользователи.

Выключаем соответствующую проверку в секции acl_check_rcpt:

  # Make sure to restrict smtp auth with some specific network.
  # accept  authenticated = *
  #         hosts = 127.0.0.1
  #         control       = submission

По поводу использования RBL-списков в интернете писалось и говорилось весьма и весьма много. Я лишь приведу пару ссылок:

Тестирование RBL на ложные срабатывания (Спамтест)
Тёмная история RBL (Журнал "Системный администратор")

Я рекомендую применять RBL-списки, но опять-таки, с умом:

  deny    message       = rejected because $sender_host_address is in a black list at $dnslist_domain\n$dnslist_text
          dnslists      = cbl.abuseat.org : bl.spamcop.net
При этом, совершенно необходимым условием такой практики является внимательное изучение политики каждого из юзаемых списков. Эти два списка имеют вполне консервативную политику, что на практике дает неплохие результаты с очень малой долей ложных срабатываний. В частности spamcop иногда "сбоит", но его используют большая часть почтовых серверов - если кто-то туда попал, ему проще решить проблему (или снести почтовый сервис), чем убедить получателей в опасности применения RBL. Abuseat в этом отношении очень надежен, так как содержит лишь инфицированные хосты. Политики этих двух списков доступны на их сайтах: bl.spamcop.net, cbl.abuseat.org.

В остальном секция acl_check_rcpt вполне годится для работы.

Роутеры

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

В default-конфигурации содержится абсолютно работоспособная конфигурация роутеров. При замене стандартной sendmail на exim, в ней скорее всего ничего менять не потребуется. Однако, мне лично не очень нравится держать под каждый из ящиков в системе домашнюю директорию, а также держать mbox-файлы с почтой в таком весьма важном разделе системы, как /var. Формат ящиков лучше поменять на maildir, а директорию /var/mail вынести в отдельный раздел диска (у меня это /mail).

Раз домашних директорий нет, то и forward файлы тоже отстутствуют по определению. Комментируем полностью роутер userforward.

#userforward:
#  driver = redirect
#  check_local_user
#  file = $home/.forward
#  no_verify
#  no_expn
#  check_ancestor
#  file_transport = address_file
#  pipe_transport = address_pipe
#  reply_transport = address_reply
#  condition = ${if exists{$home/.forward} {yes} {no} }

Роутер localuser осуществляет локальную доставку в ящики, проверяя наличие локального аккаунта пользователя. Поскольку домашних директорий в почтовых аккаунтах нет, то следует заменить опцию check_local_user таким образом, чтобы данный роутер проверял лишь наличие соответствующей записи в /etc/passwd:

localuser:
  driver = accept
#  check_local_user
  local_parts = passwd;$local_part
  transport = local_delivery
  cannot_route_message = Unknown user
На этом закончим редактирование роутеров.

Транспорты

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

Транспорт доставки через исходящие SMTP-соединения обычно используется в тех случаях, когда письмо предназначено для доставки на нелокальный адрес. Самая распостраненная правка этого транспорта - настройка на отправку всех исходящих писем через SMTP-сервер провайдера:

remote_smtp:
  driver = smtp
  hosts = smtp.my-isp.ru
  hosts_override = yes
Если у Вашего провайдера несколько транзитных почтовых серверов, то можно их прописывать сразу все через двоеточие:

  hosts = smtp1.my-isp.ru : smtp2.my-isp.ru : smtp3.my-isp.ru

Транспорт доставки в локальные ящики:

local_delivery:
  driver = appendfile
#  file = /var/mail/$local_part
  directory = /mail/$local_part/
  delivery_date_add
  envelope_to_add
  return_path_add
  group = mail
  user = $local_part
  mode = 0660
  directory_mode = 0750
#  no_mode_fail_narrower
  maildir_format
Здесь можно определить путь и режим записи писем в файлы на диск. Формат maildir представляет собой три директории: new, cur, tmp. Если во время доставки, транспорт обнаружит, что директории отсутствуют - он создаст их с правами, указанными в directory mode. А все файлы, которые будут создаваться в процессе доставки, будут иметь права из опции mode. Сервис pop3 будет работать с правами mailnull:mail, поэтому следует заранее позаботиться о нем, и разрешить группе mail читать и удалять файлы во всех ящиках. Однако директории он трогать не должен, поэтому: directory_mode = 0750. Путь до ящика берется непосредственно из опции directory (или file), или как результат маршрутизации redirect-роутером.

В остальном транспорты из default-конфигурации вполне корректны.

Файлы и директории

Директория /mail, в которой сейчас лежат ящики в формате maildir, должна разрешать exim-у создать несуществующие директории для новых ящиков:

$ chown mailnull:mail /mail
$ chmod 0770 /mail

Создавать аккаунты без домашних директорий можно с помощью такой команды:

$ pw useradd -n login -d /nonexistent -s /usr/sbin/nologin -C 'User details'

Не забудем сделать ротацию логов. Например, в /etc/newsyslog.conf:

/var/log/exim/mainlog mailnull:mail 640 30 * @T00 J
/var/log/exim/rejectlog mailnull:mail 640 30 * @T00 J
/var/log/exim/paniclog mailnull:mail 640 10 1000 * J

Антивирус

Запускаем:

$ /usr/local/etc/rc.d/clamav-clamd start
$ /usr/local/etc/rc.d/clamav-freshclam start

Exim, вероятнее всего, еще не "знает" правильного пути к антивирусу. Для этого, узнаем сначала адрес unix-сокета:

$ netstat -an |grep clam

У меня в конфиге Exim-а получилось так:

av_scanner = clamd:/var/run/clamav/clamd.sock

Проверки url-ов в списках URIBL/SURBL

Устанавливаем приложения curl и GeoIP из портов ftp/curl и net/GeoIP соответственно. Затем создаем временную папку для работы, например (я так делаю):

$ cd
$ mkdir dlext
$ cd dlext

Скачиваем исходники с сайта David Saez:

http://www.ols.es/exim/dlext/Makefile
http://www.ols.es/exim/dlext/geoip.c
http://www.ols.es/exim/dlext/getenv.c
http://www.ols.es/exim/dlext/pipe.c
http://www.ols.es/exim/dlext/surbl.c

Теперь, чтобы на FreeBSD собрать все это, необходимо слегка подправить исходники. Для начала подготовим исходные тексты exim:

$ cd /usr/ports/mail/exim
$ make

После распаковки и сборки, ищем директорию внутри work, где есть local_scan.h. У меня это work/exim-4.69/build-FreeBSD-i386.

$ cd
$ cd dlext

Теперь будем править Makefile так, чтобы он нашел все что нужно:

EXIMSRC := /usr/ports/mail/exim/work/exim-4.69/build-FreeBSD-i386
INCLUDE =-I${EXIMSRC} -I/usr/local/include

Путь /usr/local/include нужен для того, чтобы make нашел GeoIP.h. Также для сборки pipe.c нужно подключить библиотеку с сигналами - правим pipe.c:

#include <signal.h>

Все. Теперь собираем модуль и копируем его поближе к exim-у:

$ make
$ cp exim-ext.so /usr/local/etc/exim/

Осталось лишь подредактировать /usr/local/etc/exim/configure. В глобальной секции дописываем (если ее еще нет) проверку mime:

acl_smtp_mime = acl_check_mime

а в секции ACL добавляем саму проверку:

acl_check_mime:

  accept  hosts         = +relay_from_hosts

  warn  set acl_m9      = 0
        decode          = default

  warn  condition       = ${if eq{${lc:$mime_content_type}}{text/plain}}
        set acl_m9      = ${dlfunc{/usr/local/etc/exim/exim-ext.so}\
                                {surbl}{$mime_decoded_filename}}

  warn  condition       = ${if eq{${lc:$mime_content_type}}{text/html}}
        set acl_m9      = ${dlfunc{/usr/local/etc/exim/exim-ext.so}\
                                {surbl}{$mime_decoded_filename}}

  deny  !condition      = ${if eq {$acl_m9}{0}}
        message         = Bad url founded in this mail ($acl_m9).

  accept

Теперь можно запускать MTA:

$ /usr/local/etc/rc.d/exim start

Для теста URIBL, возьмем любой заблокированный url в тексте (например http://videpol.ru) и попробуем написать сами себе письмо с какого-нибудь левого почтовика.

Сервис POP3

Почему именно tpop3d, а не, скажем, popa3d (в порядке убывания важности):

  1. Можно прикручивать свою собственную схему авторизации с помощью perl-скриптов
  2. Очень легко реализуется мульти-доменный (virtual domains) доступ к ящикам
  3. Также может использовать для авторизации MySQL/PgSQL/LDAP/PAM/file/cmd
  4. Обширные возможности для конфигурирования

Итак, будем использовать PAM, т.к. учетные записи почтовых ящиков хранятся в /etc/passwd. В исходном файле конфигурации /usr/local/etc/tpop3d.conf все уже заточено для работы в стандартном режиме (ящики в формате mbox в /var/mail, авторизация через PAM). В нашем же случае надо лишь кое-что подправить.

Пусть любители ломать POP3-сервисы отдыхают (т.к. сервис будет "прослушивать" только внутренний интерфейс):

listen-address: 192.168.1.1

RFC по POP3 требует ждать неожиданного замолчавшего клиента 600 сек (10 мин). Я считаю, что это в современных условиях весьма много:

timeout-seconds: 30

Ящики в формате maildir и находятся в /mail:

mailbox: maildir:/mail/$(user)

Один ящик - один клиент: так должно быть. Если двум или более клиентам нужно получать почту, направленную на один и тот же адрес, следует воспользоваться алиасами.

maildir-exclusive-lock: true

Теперь можно запускать сервис:

$ /usr/local/etc/rc.d/tpop3d start