Страница 1 из 1

Exim+Dovecot2+PgSQL - добиваем связку

Добавлено: 03 май 2012, 14:17
Raven
Итак камрады, пришло время подвести к логическому завершению нашу связку которую я описывал в двух предыдущих постах. Для полноты картинки нам осталось только настроить самую главную часть - MTA. Естественно самый правильный по моему мнению MTA - это Exim (кто не согласен - на вкус и цвет фломастеры разные :-D ), так что делать будем на нем.

В общей картине что у нас есть? У нас есть
  • RedHat Enterprise Linux 6.2
  • Настроеный кластер PostgreSQL
  • Настроеный MDA - Dovecot2
  • Установленый Clamav
  • Установленый Spamassassin
  • Установленый Exim 4.77
Из всей этой кучи не настроеными остались 3 последних софтины. Чтож.... настроим!

Правим /etc/exim/exim.conf

Код: Выделить всё

DOMAIN_QUERY = SELECT domain FROM domain WHERE domain='${domain}' AND active=true

# БД+Хосты
hide pgsql_servers = pgpool.domain.tld/mail/mail/пароль_пользователя_БД
domainlist local_domains = ${lookup pgsql{SELECT domain FROM domain WHERE domain='${domain}' AND active=true}}
domainlist relay_to_domains = ${lookup pgsql{DOMAIN_QUERY}}
domainlist dummy_domains =
hostlist relay_from_hosts = localhost:127.0.0.1/8:<ext_if_ip>:mail.domain.tld:domain.tld
hostlist spamers = ${lookup pgsql{SELECT host FROM blacklist WHERE host='${sender_host_address}'}}


host_lookup = *   
rfc1413_hosts = * 
rfc1413_query_timeout = 0s
allow_domain_literals = false

# TLS/SSL                      
tls_advertise_hosts = *                              
tls_certificate = /etc/pki/tls/private/exim.pem   
tls_privatekey = /etc/pki/tls/private/exim.pem    
daemon_smtp_ports = 25 : 465 : 587                   
tls_on_connect_ports = 465 

# Цепляем антивирь и антиспам
spamd_address = 127.0.0.1 783
av_scanner = clamd:/tmp/clamd.sock

smtp_banner = mx.domain.tld ESMTP Postfix 2.9.1/2.9.1  (CentOS 5.3); $tod_full
local_interfaces = 127.0.0.1 : <ext_ip_if>
qualify_domain = mx.domain.tld
qualify_recipient = mx.domain.tld
primary_hostname = mx.domain.tld

never_users = root
exim_user = exim
exim_group = exim
trusted_users = lighttpd
dsn_from = Mail Delivery System <Mailer-Daemon@$qualify_domain>

# Логи, тайминги
ignore_bounce_errors_after = 1d
timeout_frozen_after = 1d
delay_warning = 4h:8h:24h:48h
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

# Тюним параметры SMTP 
smtp_accept_max = 500
smtp_accept_max_per_connection = 250
smtp_accept_max_per_host = 20
smtp_connect_backlog = 50
smtp_accept_queue_per_connection = 30
split_spool_directory = true
remote_max_parallel = 15
message_size_limit = 64M
smtp_enforce_sync = true
allow_mx_to_ip
disable_ipv6 = true
auto_thaw = 1h

# Небольшой фейк с заголовком
received_header_text = Received: \
  ${if def:sender_rcvhost {from $sender_rcvhost\n\t}\
  {${if def:sender_ident \
  {from ${quote_local_part:$sender_ident} }}\
  ${if def:sender_helo_name {(helo=$sender_helo_name)\n\t}}}}\
  by $qualify_domain \
  ${if def:received_protocol {with $received_protocol}} \
  ${if def:tls_cipher {($tls_cipher)\n\t}}\
  (Postfix 2.9.1)\n\t\
  ${if def:sender_address \
  {(envelope-from <$sender_address>)\n\t}}\
  id $message_exim_id\
  ${if def:received_for {\n\tfor $received_for}}


# Списки доступа
acl_smtp_mail = acl_check_mail
acl_smtp_dkim = acl_check_dkim
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data

begin acl

acl_check_mail:

  deny    condition = ${if eq{$sender_helo_name}{} {1}}
       message = Nice boys say HELO first
  
  warn condition = ${if eq{$sender_host_name}{} {1}}
       set acl_m_greylistreasons = Host $sender_host_address lacks reverse DNS\n$acl_m_greylistreasons
  
  accept

# Проверяем соответствие DKIM домену отправителя
acl_check_dkim:

  deny 	message 	= Mail from DKIM-domain without DKIM-signature! Wassup?!
          sender_domains = ${lookup pgsql{SELECT array_to_string(array_agg(domains), ':') AS domains FROM dkim_domains WHERE active=true}} # Запись в базе в виде | gmail.com | gmail.com |
          dkim_signers 	= ${lookup pgsql{SELECT array_to_string(array_agg(signers), ':') AS signers FROM dkim_domains WHERE active=true}}
          dkim_status 	= none:invalid:fail

  accept

acl_check_rcpt:

  accept  hosts = :
          control = dkim_disable_verify

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

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

  accept  local_parts   = postmaster
          domains       = +local_domains

  require verify        = sender

  accept  hosts         = +relay_from_hosts
          control       = submission
          control       = dkim_disable_verify

# Ratelimit - ограничиваем частоту отправляемых писем в час. Данные выбираем... конечно из базы
  warn    authenticated   = *
          ratelimit       = ${lookup pgsql{SELECT rate FROM mailbox WHERE username='${quote_pgsql:$authenticated_id}' LIMIT 1}} / 1h / strict / $authenticated_id
          log_message     = Authenticated sender rate $sender_rate / $sender_rate_period

  deny    authenticated   = *
          ratelimit       = ${lookup pgsql{SELECT rate FROM mailbox WHERE username='${quote_pgsql:$authenticated_id}' LIMIT 1}} / 1h / strict / $authenticated_id
          log_message     = Authenticated sender rate $sender_rate / $sender_rate_period

# Авторизованый пользователь минует проверку DKIM
  accept  authenticated = *
          control       = submission
          control       = dkim_disable_verify
# Наш собственный блеклист
  deny    message       = "Your address in banlist!"
          senders       = ${lookup pgsql{SELECT sender FROM blacklist WHERE sender='${quote_pgsql:$sender_address}' OR sender='*@${quote_pgsql:$sender_address_domain}' LIMIT 1}}

  deny    hosts         = +spamers
          message       = "Host rejected by spam list on rbl.domain.tld!"
# Проверка корректности
  require message = relay not permitted
          domains = +local_domains : +relay_to_domains

  require verify = recipient

  deny    message       = "HELO/EHLO required by SMTP RFC"
          condition     = ${if eq{$sender_helo_name}{}{yes}{no}}

  deny    condition     = ${if match{$sender_helo_name}{\N^\d+$\N}{yes}{no}}
          hosts         = !127.0.0.1:!localhost:*
          message       = "There can not be only numbers in HELO!"

  deny    condition     = ${if eq{$sender_address}{}{yes}{no}}
          hosts         = +relay_from_hosts
          message       = "Your message have not return address"

  deny    message   = "The use of IP is forbidden in HELO!"
          hosts     = *:!+relay_from_hosts
          condition = ${if eq{$sender_helo_name}\
                      {$sender_host_address}{true}{false}}

  deny    condition = ${if eq{$sender_helo_name}\
                      {$interface_address}{yes}{no}}
          hosts     = !127.0.0.1 : !localhost : *
          message   = "The use of my IP is forbidden!"

# хосты на динамике нам тоже ни к чему
  deny    message   = "Dynamic hosts is forbidden!"
          condition = ${if match{$sender_host_name}\
                     {dsl|dial|pool|peer|dhcp|cable} {yes}{no}}

# Если домен отправителя имеет SPF-запись смотрим соответствует ли она IP-адресу с которого пришло письмо (требуется пакет libspf2)
  deny    message     = [SPF] $sender_host_address is not allowed to send mail \
                  from $sender_address_domain.
          log_message = SPF check failed.
          set acl_m9  = -ipv4=$sender_host_address \
                  -sender=$sender_address \
                  -helo=$sender_helo_name
          set acl_m9  = ${run{/usr/bin/spfquery $acl_m9}}
          condition   = ${if eq {$runrc}{1}{true}{false}}

# Смотрим нет ли отправителя в блеклистах
  deny    message       = rejected because $sender_host_address is in a black list at $dnslist_domain\n$dnslist_text
          hosts         = !+relay_from_hosts
          !authenticated = *
          log_message   = found in $dnslist_domain
          dnslists      = ${lookup pgsql{SELECT array_to_string(array_agg(dnsblists), ':') AS test FROM dnsbl WHERE bl=true}}

  accept

# Проверка тела письма
acl_check_data:

  warn    condition  = ${if !def:h_Message-ID: {1}}
          set acl_m_greylistreasons = Message lacks Message-Id: header. Consult RFC2822.\n$acl_m_greylistreasons

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

  accept  condition  = ${if >={$message_size}{100000} {1}}
           add_header = X-Spam-Note: SpamAssassin run bypassed due to message size

  warn    spam       = nobody/defer_ok
           add_header = X-Spam-Flag: YES
  
  accept  condition  = ${if !def:spam_score_int {1}}
           add_header = X-Spam-Note: SpamAssassin invocation failed

  warn    add_header = X-Spam-Score: $spam_score ($spam_bar)\n\
                        X-Spam-Report: $spam_report

  deny       condition = ${if >{$spam_score_int}{100} {1}}
           message   = Your message scored $spam_score SpamAssassin point. Report follows:\n\
    	    	        $spam_report

  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

# Проверяем не отключен-ли домен пользователя
disabled_domains:
	driver = redirect
	condition = ${lookup pgsql{SELECT domain FROM domain WHERE domain='${domain}' AND active=false}}
	allow_fail = yes
	data = :fail: Domain disabled
	no_more

# Проверяем не отключен-ли пользователь
disabled_users:
	driver = redirect
	condition = ${lookup pgsql{SELECT username FROM mailbox WHERE username='${quote_pgsql:$local_part@$domain}' AND active=false}}
	allow_fail = yes
	data = :fail: User disabled
	no_more

# Проверим не является ли пользователь алиасом
system_aliases:
	driver = redirect
	allow_fail
	allow_defer
	data = ${lookup pgsql{SELECT goto FROM alias WHERE address='${quote_pgsql:$local_part@$domain}' OR address='${quote_pgsql:@$domain}'}}

# Проверим существует-ли пользователь, если да письмо проходит дальше в транспортировщик к MDA
dovecot_user:
	driver = accept
	condition = ${if eq{} {${lookup pgsql{SELECT username FROM mailbox WHERE local_part='${local_part}' AND domain='${domain}' AND active=true}}}{no}{yes}}
	transport = dovecot_delivery

# Транспорты
begin transports

# Определим какой домен юзать для подписи DKIM
DKIM_DOMAIN      = ${lc:${domain:$h_from:}}

remote_smtp:
      driver = smtp
      interface = <ext_if_ip>
# Подписываем исходящую почту DKIM-ключом
      dkim_domain = DKIM_DOMAIN
      dkim_selector = delta
      dkim_private_key = ${lookup pgsql{SELECT rsa_key FROM dkim WHERE domain='${quote_pgsql:$dkim_domain}' LIMIT 1}{$value}}
      dkim_canon = relaxed

# Доставка в dovecot посредством LDA
dovecot_delivery:
    driver = pipe
    command = /usr/libexec/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 = mail

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

# Авторизация пользователей. Мы юзаем Dovecot 
begin authenticators

auth_plain:
 driver = dovecot
 public_name = PLAIN
 server_socket = /var/run/dovecot/auth-client
 server_set_id = $auth1

auth_login:
 driver = dovecot
 public_name = LOGIN
 server_socket = /var/run/dovecot/auth-client
 server_set_id = $auth1

auth_cram_md5:
 driver = dovecot
 public_name = CRAM-MD5
 server_socket = /var/run/dovecot/auth-client
 server_set_id = $auth1
Заполняем базу по своему усмотрению и стартуем сервисы

Код: Выделить всё

# service clamd start
# service spamassassin start
# service exim start
И пробуем отправить/ получить письма во все стороны)))

Ах, да... совсем забыл - DKIM!!!
Сгенерим пару ключей:

Код: Выделить всё

#openssl genrsa -out dkim.private.key 768
#openssl rsa -in dkim.private.key -out dkim.public.key -pubout -outform PEM
Содержимое dkim.private.key запишем в базу (все как есть, ничего не изменяя) в виде |domain.tld | <ключ> |
Содержимое dkim.public.key (только строчки с данными в одну строчку, коментарии нам не нужны) внесем в конфигу нашей зоны dns в виде текстовой записи

Код: Выделить всё

delta._domainkey.domain.tld.   TXT v=DKIM1; t=y; k=rsa; p=<ключ>
обратите внимание на слово delta - это селектор, описаный нами в секции remote_smtp конфига exim. Они должны совпадать!