exim+courier-imap+mysql+postfixadmin

Установка полноценного почтового сервера Exim в связке Courier-IMAP + MySQL, в качестве админки будем прикручивать Postfixadmin. Использование виртуальных пользователей хорошо скажется на системной безопасности.

cd /usr/ports/mail/exim
make install clean

Опции сборки exim:


WITHOUT_ALT_CONFIG_PREFIX=true
WITH_AUTH_CRAM_MD5=true
WITH_AUTH_DOVECOT=true
WITH_AUTH_PLAINTEXT=true
WITHOUT_AUTH_RADIUS=true
WITHOUT_AUTH_SASL=true
WITH_AUTH_SPA=true
WITH_CDB=true
WITHOUT_CONTENT_SCAN=true
WITH_DAEMON=true
WITH_DCC=true
WITHOUT_DEBUG=true
WITH_DISABLE_D_OPT=true
WITH_DNSDB=true
WITH_DSEARCH=true
WITH_EMBEDDED_PERL=true
WITHOUT_EXIMON=true
WITH_ICONV=true
WITH_IPV6=true
WITHOUT_KAS=true
WITHOUT_LISTMATCH_RHS=true
WITH_LMTP=true
WITH_LSEARCH=true
WITH_MAILDIR=true
WITH_MAILSTORE=true
WITH_MBX=true
WITH_MYSQL=true
WITH_NIS=true
WITH_OLD_DEMIME=true
WITHOUT_OPENLDAP=true
WITH_PAM=true
WITHOUT_PASSWD=true
WITHOUT_PGSQL=true
WITH_READLINE=true
WITHOUT_SASLAUTHD=true
WITHOUT_SA_EXIM=true
WITHOUT_SO_1024=true
WITH_SPF=true
WITHOUT_SQLITE=true
WITH_SRS=true
WITH_SRS_ALT=true
WITH_SUID=true
WITHOUT_TCP_WRAPPERS=true
WITH_TLS=true
WITHOUT_WISHLIST=true
WITHOUT_XCLIENT=true

Далее пилим конфиг /usr/local/etc/exim/configure до следующего состояния:

#!/bin/sh
# Имя хоста. Используется в EHLO.
primary_hostname = хост
# хост/имя_бд/пользователь/пароль
hide mysql_servers = хост/имя_бд/пользователь/пароль
# Список локальных доменов.
# Будет в виде +local_domains
domainlist local_domains = ${lookup mysql{SELECT `domain` \FROM `domain` WHERE \`domain`='${domain}' AND \`active`='1'}}
# Список доменов с которых разрешены релеи.
# Будет в виде +relay_to_domains
domainlist relay_to_domains = ${lookup mysql{SELECT `domain` \FROM `domain` WHERE \`domain`='${domain}' AND \`active`='1'}}
# Список хостов с которых разрешён не авторизованый релей.
hostlist relay_from_hosts = localhost : 127.0.0.0/8
# Вводим названия acl`ов для проверки почты. 
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data
# Имя домена добавляемое для локальных отправителей (реальных
# юзеров системы) - почта отправляемая от root, будет от
# root@qualify_domain.
qualify_domain = хост
# Имя хоста для ситуации, обратной предыдущей, - это имя домена
# добавляемое к почте для системных юзеров.
qualify_recipient = хост
# Порт SMTPdaemon_smtp_ports = 25
# почта user@ип_адрес - принимать её или нет.
allow_domain_literals = false
# Пользователь от которого работает exim
exim_user = mailnull
# Группа в которой работает exim
exim_group = mail
# Запрещаем доставку под юзером root - для безопасности
never_users = root
# Проверяем соответствие прямой и обратной зон для всех хостов.
host_lookup = *
# Если hosts поставить * то будет проверять все. Таймаут -
# если поставить 0 то не будет ждать ответа ни от кого. 
# для безопасности отключаем
#rfc1413_hosts = *
rfc1413_query_timeout = 0s
# Если сообщение об ошибке не удалось доставить
# то оно замораживается на указанный срок.
ignore_bounce_errors_after = 60m
# Замороженные сообщения хранятся этот срок
# затем удаляются и генерируется сообщение об ошибке
timeout_frozen_after = 7d
# список адресов (через запятую) на которые засылаются
# сообщения о замороженных сообщениях
freeze_tell = адрес_почты
# Список хостов, почта от которых принимается, несмотря
# на ошибки в HELO/EHLO
helo_accept_junk_hosts = 127.0.0.0/8
# Через какое время повторять попытку доставки
# замороженного сообщенияauto_thaw = 1h
# Приветствие сервера при коннекте
smtp_banner = "$primary_hostname, ESMTP EXIM $version_number"

# Максимальное число одновременных подключений по
# SMTP. Рассчитывать надо исходя из нагрузки на сервер
smtp_accept_max = 100
# Максимальное число сообщений принимаемое за одно соединение
# от удалённого сервера (или пользователя).
smtp_accept_max_per_connection = 100
# Максимальное число сообщений записываемых в логи
smtp_connect_backlog = 100
# Максимальное число коннектов с одного хоста
smtp_accept_max_per_host = 100
# Для увеличения производительности,
# директория `spool` внутри, разбивается на
# директории - это ускоряет обработку
split_spool_directory = true
# Если у сообщения много адресатов на удалённых хостах,
# то запускатеся до указанного числа максимально число
# параллельных процессов доставки
remote_max_parallel = 100
# При генерации сообщения об ошибке прикладывать
# не всё сообщение, а кусок от начала указанного
# размера (если целиком высылать
# закомментируйте строку)
return_size_limit = 0k
# Максимальный размер сообщения. 
# (если 0 - то анлим)
message_size_limit = 0
# Принудительная синхронизация. Если отправитель
# торопится посылать команды, не ожидая ответа, спамер
smtp_enforce_sync = true
# Выбираем, что будем логировать
# + - писать в логи,
# - - Не писать в логи.
# +all_parents - все входящие?
# +connection_reject - разорваные соединения
# +incoming_interface - интерфейс (реально - IP)
# +lost_incoming_connections - потеряные входящие
# соединения
# +received_sender - отправитель
# +received_recipients - получатель
# +smtp_confirmation - подтверждения SMTP?
# +smtp_syntax_error - ошибки синтаксиса SMTP
# +smtp_protocol_error - ошибки протокола SMTP
# -queue_run - работа очереди (замороженные мессаги)
log_selector = \
+all_parents \
+connection_reject \
+incoming_interface \
+lost_incoming_connection \
+received_sender \
+received_recipients \
+smtp_confirmation \
+smtp_syntax_error \
+smtp_protocol_error \
-queue_run
# Убираем собственную временную метку exim`a из логов, её ставит
# сам syslogd
syslog_timestamp = no
begin acl
# Эти правила срабатывают для каждого получателя
acl_check_rcpt:
# принимать сообщения которые пришли с локалхоста,
# не по TCP/IP
accept hosts = :
# Запрещаем письма содержащие в локальной части
# символы @; %; !; /; |. Учтите, если у вас было
# `percent_hack_domains` то % надо убрать.
# Проверяются локальные домены
deny message = "Restricted characters in address! Access denied!"
domains = +local_domains
local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./
# Проверяем недопустимые символы для
# нелокальных получателей:
deny message = "Restricted characters in address! Access denied!"
domains = !+local_domains
local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./
# Запрещаем почту из вне на root
deny message = "User not found! Access denied!"
domains = +local_domains
local_parts = root
# Запрещщаем, если невозможно проверить отправителя
# Эти пользователи отсутствуют в списке локальных пользователей,
# железяки (принтеры, & etc) и программы (Касперский, DrWEB)
# умеют слать почту, в случае проблем, но не умеют ставить
# нужного отправителя. Такие письма эта проверка не пускает.
require verify = sender
# Запрещщаем тех, кто не обменивается приветственными
# сообщениями (HELO/EHLO)
deny message = "HELO/EHLO not by SMTP RFC! Access denied!"
condition = ${if eq{$sender_helo_name}{}{yes}{no}}
# Принимаем сообщения от тех, кто аутентифицировался:
accept authenticated = *
# Блокируем тех, кто подставляет свой IP в HELO
deny message = "Your IP in HELO! Access denied!"
hosts = * : !+relay_from_hosts
condition = ${if eq{$sender_helo_name}\{$sender_host_address}{true}{false}}

# Блокируем тех, кто в HELO пихает мой IP
deny condition = ${if eq{$sender_helo_name}\{$interface_address}{yes}{no}}
hosts = !127.0.0.1 : !localhost : *
message = "Main IP in your HELO! Access denied!"
# Блокируем тех, кто в HELO пихает только цифры
deny condition = ${if match{$sender_helo_name}\{\N^\d+$\N}{yes}{no}}
hosts = !127.0.0.1 : !localhost : *
message = "Can not be only number in HELO! Access denied!"
# Блокируем с недопустимымми символами в helo
deny condition = ${if match{$sender_helo_name}{\N_\N}{yes}{no}}
hosts = !127.0.0.1 : !localhost : !+relay_from_hosts : *
!senders = :
message = "HELO contain '_'! Access denied!"
# Проверяем, нашлась ли обратная запись для этого хоста
deny condition = ${if eq{$host_lookup_failed}{1}{yes}{no}}
hosts = !+relay_from_hosts
!senders = :
message = "You not have reverse zone for you host! Access denied!"
# Проверка получателя в локальных доменах.
# Если не проходит, то проверяется следующий ACL,
# и если непрошёл и там - deny
accept domains = +local_domains
endpass
message = "User not found! Access denied!"
verify = recipient
# Проверяем получателя в релейных доменах
# Опять-таки если не проходит -> следующий ACL,
# и если непрошёл и там - deny
accept domains = +relay_to_domains
endpass
message = "Address not found! Access denied!"
verify = recipient
# Разрешаем почту от доменов в списке relay_from_hosts
accept hosts = +relay_from_hosts
# Если неподошло ни одно правило - ищут
# открытый релей.
deny message = "Relay not permitted! Access denied!"

# Тут идут ACL проверяющие содержимое (тело) письма.
# Без них будут пропускаться все сообщения.
acl_check_data:
# Если есть необходимость - тут проверки на спам
# Проверки после команды DATA, но до приёма письма
# Прибиваем mailer-daemon
deny message = "Unknown mailer-daemon! Access denied!"
condition = ${if match{$recipients}{mailer-daemon}{yes}{no}}
# Блокируем с пустым отправителями (<>) используется
# для проверки существования E-Mail'а
deny message = "Unknown user! Access denied!"
senders = :
accept
# Блокируем спам, отправляемый как рикошет
deny message = Message that generate bounce not coming from main hosts
condition = ${if !match{$message_body}{X-Bounce-ID}{yes}{no}}
condition = ${if eq{$sender_address}{}{yes}{no}}
# Блокируем сообщения с NULL-символами
deny message = "Message contains NULL characters! Access denied!"
!senders = :
# log_message = NULL characters!
condition = ${if >{$body_zerocount}{0}{1}{0}}
# Блокируем с неправильным синтаксисом заголовков
deny message = "Incorrect headers syntax! Access denied!"
hosts = !+relay_from_hosts!
senders = :
!verify = header_syntax
# Пропускаем остальноеaccept
begin routers
# Поиск маршрута к хосту в DNS. Если маршрут не найден в DNS -
# то это `унроутабле аддресс`. Не проверяются локальные
# домены, 0.0.0.0 и 127.0.0.0/8
dnslookup
:driver = dnslookup
domains = ! +local_domains
transport = remote_smtp
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
no_more
local_aliases:
driver = redirect
allow_fail
allow_defer
data = ${lookup{$local_part}lsearch{/etc/aliases}}
#system_aliases:
# driver = redirec
t# allow_fail
# allow_defer
# data = ${lookup mysql{SELECT `goto` FROM `alias` WHERE \
# `address`='${quote_mysql:$local_part@$domain}' OR \
# `address`='${quote_mysql:@$domain}'}}
mysqluser:
driver = accept
condition = ${if eq{}{${lookup mysql{SELECT `maildir` FROM `mailbox` \WHERE `username`='${quote_mysql:$local_part@$domain}'}}}{no}{yes}}
transport = mysql_delivery
# Транспорты доставки почты
begin transports
# Доставка на удалённые хосты - по SMTP
remote_smtp:
driver = smtp
mysql_delivery:
driver = appendfile
check_string = ""
create_directory
delivery_date_add
directory = ${lookup mysql{SELECT CONCAT('/путь_к_маил_папке_почтаря/', `maildir`) \FROM `mailbox` WHERE `username`='${local_part}
@${domain}'}}
directory_mode = 770
envelope_to_add
group = mail
maildir_format = true
maildir_tag = ,S=$message_size
message_prefix = ""
message_suffix = ""
mode = 0600
address_file:
driver = appendfile
delivery_date_add
envelope_to_add
return_path_add
# Имя программы
address_pipe:
driver = pipe
return_output
# Транспорт для автоответов
address_reply:
driver = autoreply
 
# Повторы недоставленных писем.
begin retry
# Тут циферки разработчиков* * F,2h,15m; G,16h,1h,1.5; F,4d,6h
# преобразование адресов.
begin rewrite

# Секция авторизации клиентов при отправке писем.
# почтовых клиентов много, механизмов авторизации три
begin authenticators
# Первый метод авторизации.
auth_plain:
driver = plaintext
public_name = PLAIN
server_condition = ${lookup mysql{SELECT `username` FROM \`mailbox` WHERE `username` = \'${quote_mysql:$1}' AND `password` = \
'${quote_mysql:$2}'}{yes}{no}}
server_prompts = :
server_set_id = $2
# Второй метод авторизации.
auth_login:
driver = plaintext
public_name = LOGIN
server_condition = ${lookup mysql{SELECT `username` FROM \`mailbox` WHERE `username` = \'${quote_mysql:$1}' AND `password` = \
'${quote_mysql:$2}'}{yes}{no}}
server_prompts = Username:: : Password::
server_set_id = $1
# Третий метод авторизации.
auth_cram_md5:
driver = cram_md5
public_name = CRAM-MD5
server_secret = ${lookup mysql{SELECT `password` FROM \`mailbox` WHERE `username` \= '${quote_mysql:$1}'}{$value}fail}
server_set_id = $1

права на /usr/local/etc/exim/configure должны быть root:wheel
права на маил_папку_почтаря mailnull:mail

Далее пилим /etc/mail/mailer.conf 

sendmail /usr/local/sbin/exim
send-mail /usr/local/sbin/exim
mailq /usr/local/sbin/exim -bp
newaliases /usr/local/sbin/exim -bi
hoststat /usr/local/sbin/exim
purgestat /usr/local/sbin/exim

Далее натягиваем админку postfixadmin для нашего почтаря

cd /usr/ports/mail/postfixadmin/
make install clean

Сливаем его на хостинг и правим postfixadmin/config.inc.phpвыставляем свои данные по коннекту к БД, правим под себя и не забываем изменить

$CONF['encrypt'] = 'cleartext';
$CONF['configured'] = true;

После запила, продолжаем установку:

cd /usr/ports/mail/courier-imap
make install clean

Опции сборки courier-imap:

WITH_FAM=true
WITH_TRASHQUOTA=true
WITH_GDBM=true
WITH_IPV6=true
WITHOUT_DRAC=true
WITHOUT_AUTH_LDAP=true
WITH_AUTH_MYSQL=true
WITHOUT_AUTH_PGSQL=true
WITHOUT_AUTH_USERDB=true
WITHOUT_AUTH_VCHKPW=true

Пилим /usr/local/etc/authlib/authmysqlrc

# хост на котором находится сервер БД
MYSQL_SERVER хост
# Имя пользователя для соединения с БД
MYSQL_USERNAME пользователь
# Пароль для соединения с БД
MYSQL_PASSWORD пароль
# Порт на котором работает MySQL (при сетевом соединении)
MYSQL_PORT 3306
# Имя базы данных
MYSQL_DATABASE имя_базы
# Таблица из которой будем делать выборки
MYSQL_USER_TABLE `mailbox`
# поле где храниться пароль в открытом виде
MYSQL_CLEAR_PWFIELD `password`
# поле где храниться UID 
MYSQL_UID_FIELD 26
# тоже самое что и пердыдущий пункт, только группа
MYSQL_GID_FIELD 26
# Имя (логин) пользователя - имя колонки
MYSQL_LOGIN_FIELD `username`
# Имя колонки с полням имененм пользователя (Дуня Кулакова)
MYSQL_NAME_FIELD `name`
# Директория где храниться почта пользователя
MYSQL_MAILDIR_FIELD CONCAT('/путь_к_маил_папке_почтаря/', `maildir`)
# Домашняя директория пользователя
MYSQL_HOME_FIELD CONCAT('/путь_к_маил_папке_почтаря/', `maildir`)

Теперь пилим /etc/rc.conf

echo 'exim_enable="YES"' >> /etc/rc.conf
echo 'sendmail_enable="NONE"' >> /etc/rc.conf
echo 'courier_authdaemond_enable="YES"' >> /etc/rc.conf
echo 'courier_imap_pop3d_enable="YES"' >> /etc/rc.conf

Далее запускаем наших демонов: exim, courier_authdaemond, courier_imap_pop3d и останавливаем sendmail.
Идем в web админку почтаря и произведем заливку почтового дампа в БД и настроим админа и домен (если конфиг админки запилили правильно, установка пройдет как по маслу), открываем в браузере:

http://ваше_имя/путь_к_postfixadmin/setup.php

После установки не забываем впилить строку предложенную в установщике в postfixadmin/config.inc.php
Установка и запил завершены.