MTA Exim на сервере FreeBSD c авторизацией в MySQL

Несмотря на кажущуюся простоту e-mail в недрах сервера кроется довольно сложная архитектура. Для пользователя самым знакомым компонентом является MUA (Mail User Agent) — программа для получения/хранения/отправки почтовых сообщений. Это, к примеру, Microsoft Outlook, Thunderbird, веб-интерфейс Roundcube или же любая другая программа, с помощью которой пользователь может получить и отправить электронное письмо. На сервере же существуют ещё два отдельных компонента. Это MTA (Mail Transport Agent) — программа для передачи сообщения от одного компьютера к другому и MDA (Mail Delivery Agent) — программа, принимающая почтовые электронные письма и доставляющая их на электронный адрес получателя.
В данном материале мы будем рассматривать установку и настройку MTA Exim.

Проверяем все доменные записи для почты на корректность:

# host -t MX it-studio.pro
it-studio.pro mail is handled by 15 mail.it-studio.pro.

Отлично, теперь смотрим IP этой записи

# host -t A mail.it-studio.pro
mail.it-studio.pro has address 144.76.170.161

И проверяем обратную зону

# host -t PTR 144.76.170.161
161.170.76.144.in-addr.arpa domain name pointer mail.it-studio.pro.

Отлично, подготовительные работы закончились не начавшись 🙂
Устанавливаем из портов

# cd /usr/ports/mail/exim-mysql/
# make -DBATCH install clean

После установки Exim любезно предоставляет некоторую полезную информацию

To use Exim instead of sendmail on startup:

*) Clear the sendmail queue and stop the sendmail daemon.
*) Adjust mailer.conf(5) as appropriate.
*) Set the 'sendmail_enable' rc.conf(5) variable to 'NONE'.
*) Set the 'daily_status_include_submit_mailq' and
   'daily_clean_hoststat_enable' periodic.conf(5)
   variables to 'NO'.
*) Consider setting 'daily_queuerun_enable' and
   'daily_submit_queuerun' to "NO" in periodic.conf(5),
   if you intend to manage queue runners / deliveries closely.
*) Set the 'exim_enable' rc.conf(5) variable to 'YES'.
*) Start exim with '/usr/local/etc/rc.d/exim start'.

You may also want to configure newsyslog(8) to rotate Exim log files:

/var/log/exim/mainlog	mailnull:mail 640 7 * @T00 ZN
/var/log/exim/rejectlog	mailnull:mail 640 7 * @T00 ZN 

Приводим конфигурационный файл к такому виду. Выделенные параметры необходимо заменить на свои

primary_hostname = mail.it-studio.pro
domainlist local_domains = ${lookup mysql{SELECT domain FROM domains WHERE domain='${domain}' AND (type='LOCAL' OR type='VIRTUAL')}}
domainlist relay_to_domains = ${lookup mysql{SELECT domain FROM domains WHERE domain='${domain}' AND type='RELAY'}}
hostlist spamers = ${lookup mysql{SELECT senders FROM blacklist_host WHERE senders='${sender_host_address}'}}
hostlist   relay_from_hosts = localhost : 127.0.0.1 : 144.76.170.161
GET_QUOTA=${lookup mysql{SELECT quota FROM users WHERE login='${local_part}' AND domain='${domain}'}{${value}M}}
MAILDIR_SIZE=${eval:${sg{${sg{${readfile{/var/exim/$domain/$local_part/maildirsize}{\n}}}{\N^.+?\n\N}{}}}{\N(?s)\s+-?\d+\n\N}{+}}0+500K}

daemon_smtp_ports = 25 : 465
tls_on_connect_ports = 465
tls_advertise_hosts = *
tls_certificate = /etc/ssl/certs/mail.pem
tls_privatekey = /etc/ssl/certs/mail.pem

log_selector = \
        +all_parents \
        +lost_incoming_connection \
        +received_sender \
        +received_recipients \
        +smtp_confirmation \
        +smtp_syntax_error \
        +smtp_connection \
        +smtp_protocol_error \
        -queue_run


syslog_timestamp = no

keep_environment =
add_environment =

acl_smtp_rcpt = acl_check_rcpt
acl_smtp_mime = acl_check_mime
acl_smtp_data = acl_check_data

trusted_users = www

qualify_domain = it-studio.pro

local_interfaces = 127.0.0.1 : 144.76.170.161

allow_domain_literals = false

exim_user = mailnull 
exim_group = mail

never_users = root

delay_warning = 4h:8h:24h:48h

return_size_limit = 50k

host_lookup = *

rfc1413_hosts = * rfc1413_query_timeout = 0s

smtp_enforce_sync = true

syslog_duplication = false

allow_mx_to_ip

ignore_bounce_errors_after = 2d

timeout_frozen_after = 2d

message_size_limit = 20M

smtp_accept_max = 100

smtp_accept_max_per_connection = 50

smtp_accept_max_per_host = 20

smtp_connect_backlog = 50

smtp_accept_queue_per_connection = 30

remote_max_parallel = 15

split_spool_directory = true

smtp_banner = "$primary_hostname ESMTP Exim"

hide mysql_servers = localhost/exim/exim/Password_Here

############################################################################
#                            ACL CONFIGURATION                             #
#           Specifies access control lists for incoming SMTP mail          #
############################################################################
begin acl

acl_check_rcpt:

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

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


accept senders=${lookup mysql{SELECT senders FROM whitelist WHERE senders='${quote_mysql:$sender_address}' OR senders='*@${quote_mysql:$sender_address_domain}' LIMIT 1}}

deny message = "Your address in banlist!"
     senders=${lookup mysql{SELECT senders FROM blacklist WHERE senders='${quote_mysql:$sender_address}' OR senders='*@${quote_mysql:$sender_address_domain}' LIMIT 1}}

deny hosts = +spamers
     message = "Host rejected by spamers list on rbl.it-studio.pro!"

accept authenticated = *


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}}

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      = bl.spamcop.net : \
                        cbl.abuseat.org : \
                        dnsbl.njabl.org : \
                        sbl-xbl.spamhaus.org : \
                        pbl.spamhaus.org


warn
set acl_m0 = 25s

warn
hosts = +relay_from_hosts
set acl_m0 = 0s

warn
authenticated = *
set acl_m0 = 0s

warn

logwrite = Delay $acl_m0 for $sender_host_name [$sender_host_address] with HELO=$sender_helo_name. Mail from $sender_address to $local_part@$domain.
delay = $acl_m0
drop   message     = Rejected - Sender Verify Failed
       log_message = Rejected - Sender Verify Failed
       hosts       = *
       !verify     = sender/no_details/callout=2m,defer_ok
       !condition  =  ${if eq{$sender_verify_failure}{}}
accept domains    = +local_domains
       endpass
       message    = $acl_verify_message
       verify     = recipient
accept domains  = +relay_to_domains
       endpass
       message  = "Unrouteable address!"
       verify   = recipient/callout=30s,defer_ok,use_postmaster
accept  hosts         = +relay_from_hosts
accept  authenticated = *
deny    message       = relay not permitted

accept


acl_check_mime:

deny message = Blacklisted file extension detected ($mime_filename)
condition = ${if match {${lc:$mime_filename}} {\N(\.exe|\.pif|\.bat|\.scr|\.lnk|\.com|\.vbs|\.cpl)$\N}{1}{0}}
accept 

acl_check_data: 

#deny     message  = This message contains a virus ($malware_name).
#demime   = *
#malware  = */defer_ok

accept

######################################################################
#                      ROUTERS CONFIGURATION                         #
#               Specifies how addresses are handled                  #
######################################################################
#     THE ORDER IN WHICH THE ROUTERS ARE DEFINED IS IMPORTANT!       #
# An address is passed to each router in turn until it is accepted.  #
######################################################################
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

system_aliases:
  driver = redirect
  allow_fail
  allow_defer
  data = ${lookup mysql{SELECT recipients FROM aliases WHERE (local_part='${local_part}' AND domain='${domain}') OR (local_part='*' AND domain='$domain')ORDER BY local_part='*' LIMIT 1}}

userforward:
  driver = redirect
  check_local_user=false
  file = /var/exim/$domain/$local_part/forward
  user = mailnull
  group = mail
  allow_filter
  no_verify
  no_expn
  check_ancestor
  file_transport = address_file
  pipe_transport = address_pipe
  reply_transport = address_reply
  condition = ${if exists{/var/exim/$domain/$local_part/forward}{yes}{no}}

virtual_user_quota_defer:
  driver          = redirect
  domains         = +local_domains
  condition       = ${if and{\
                    {exists{/var/exim/$domain/$local_part}}\
                    {exists{/var/exim/$domain/$local_part/maildirsize}}\
                    {>{GET_QUOTA}{0}}\
                    {>={MAILDIR_SIZE}{GET_QUOTA}}\
                    } }
  data            = :fail: Over quota!
  verify_sender = false
  allow_fail


virtual_localuser:
  driver = accept
  domains = ${lookup mysql{SELECT domain from domains WHERE domain='${domain}'}}
  local_parts = ${lookup mysql{SELECT login from users WHERE login='${local_part}' AND domain='${domain}'}}
  transport = local_delivery
  cannot_route_message = Unknown user

begin transports

remote_smtp:
  driver = smtp
  interface =  144.76.170.161

local_delivery:
  driver = appendfile
  maildir_use_size_file
  check_string = ""
  create_directory
  delivery_date_add
  directory = ${lookup mysql{SELECT LOWER(CONCAT('/var/exim/$domain/',login)) FROM users WHERE login='${local_part}' AND domain='${domain}';}}
  directory_mode = 770
  envelope_to_add
  group = mail
  maildir_format
  maildir_tag = ,S=$message_size
  message_prefix = ""
  message_suffix = ""
  mode = 0660
  quota = ${lookup mysql{SELECT quota FROM users WHERE login='${local_part}' AND domain='${domain}'}{${value}M}}
  quota_size_regex = S=(\d+)$
  quota_warn_threshold = 80%
  return_path_add

address_pipe:
   driver = pipe
   return_output

address_file:
  driver = appendfile
  delivery_date_add
  envelope_to_add
  return_path_add

address_reply:
  driver = autoreply


null_transport:
    driver = appendfile
    file = /dev/null

begin retry

begin rewrite

begin authenticators

fixed_login:
 driver = plaintext
 public_name = LOGIN
 server_prompts = Username:: : Password::
 server_condition = "${if and { \
                      {!eq{$1}{}} \
                      {!eq{$2}{}} \
                      {crypteq{$2}{\\{crypt\\}${lookup mysql{SELECT \
                      password FROM users \
                      WHERE login='${local_part:$1}' \
                      AND domain='${domain:$1}' AND \
                      smtp_auth='1'}{$value}fail}}} \
                       } {yes}{no}}"
 server_set_id = $1

fixed_plain:
 driver = plaintext
 public_name = PLAIN
 server_prompts = :
 server_condition = "${if and { \
                      {!eq{$2}{}} \
                      {!eq{$3}{}} \
                      {crypteq{$3}{\\{crypt\\}${lookup mysql{SELECT \
                      password FROM users \
                      WHERE login='${local_part:$2}' \
                      AND domain='${domain:$2}' AND \
                      smtp_auth='1'}{$value}fail}}} \
                      } {yes}{no}}"
 server_set_id = $2

Создаём базу, и настраиваем доступ к ней

# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 145
Server version: 5.6.34 Source distribution

Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> create database exim;
Query OK, 1 row affected (0.00 sec)

mysql> grant all privileges on exim.* to exim@localhost identified by 'Password_Here';
Query OK, 0 rows affected (0.00 sec)

mysql> \q
Bye 

Структуру базы выкладываю дампом. Скачиваем и импортируем

# fetch https://tradenark.com.ua/files/exim.sql
# mysql -uexim -p exim < exim.sql
Enter password:

База и её структура есть. Теперь наполним её данными.

# mysql -uexim -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 149
Server version: 5.6.34 Source distribution

Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> use exim;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> INSERT INTO `exim`.`aliases` (`local_part`, `domain`, `recipients`) VALUES ('root', 'it-studio.pro', 'support@it-studio.pro');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO `exim`.`blacklist` (`senders`, `when_added`) VALUES ('123@mail.ru', CURDATE());
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO `exim`.`blacklist_host` (`senders`, `when_added`) VALUES ('12.134.36.100', CURDATE());
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO `exim`.`domains` (`domain`, `type`) VALUES ('it-studio.pro', 'LOCAL');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO `exim`.`users` (`login`, `name`, `password`, `uid`, `gid`, `domain`, `quota`, `status`, `smtp_auth`) VALUES ('boss', 'Sales', ENCRYPT('account_password'), '26', '6', 'it-studio.pro', '450', '1', '1');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO `exim`.`whitelist` (`senders`, `when_added`) VALUES ('gleb@tradenark.com.ua', CURDATE());
Query OK, 1 row affected (0.00 sec)
mysql> \q
Bye

Теперь сгенерируем сертификаты и скроем их от посторонних глаз

# mkdir /etc/ssl/certs && cd /etc/ssl/certs
# openssl req -new -x509 -days 3650 -nodes -out /etc/ssl/certs/mail.pem -keyout /etc/ssl/certs/mail.pem
Generating a 1024 bit RSA private key
.................................................++++++
..........++++++
writing new private key to '/etc/ssl/certs/mail.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:UA
State or Province Name (full name) [Some-State]:Odeska oblast
Locality Name (eg, city) []:Bilgorod-Dnistrovsky
Organization Name (eg, company) [Internet Widgits Pty Ltd]:IT-Studio PRO
Organizational Unit Name (eg, section) []:IT Department
Common Name (e.g. server FQDN or YOUR name) []:mail.it-studio.pro
Email Address []:support@it-studio.pro
# chown mailnull:mail mail.pem
# chmod 440 mail.pem

Создаём каталог для хранения почты и ставим корректные разрешения

# mkdir /var/exim && chown mailnull:mail /var/exim 

Приводим файл /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

Первый запуск

# /usr/local/etc/rc.d/exim restart
exim not running? (check /var/run/exim.pid).
Starting exim.
# cat /var/log/exim/mainlog
2017-01-17 12:33:24 exim 4.88 daemon started: pid=64421, -q30m, listening for SMTP on [127.0.0.1]:25 [144.76.170.161]:25 and for SMTPS on [127.0.0.1]:465 [144.76.170.161]:465

Теперь можно проверить работоспособность сервера выполнит простую отправку письма

# telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mail.it-studio.pro ESMTP Exim
ehlo mail.it-studio.pro
250-mail.it-studio.pro Hello localhost [127.0.0.1]
250-SIZE 20971520
250-8BITMIME
250-PIPELINING
250-AUTH LOGIN PLAIN
250-CHUNKING
250-STARTTLS
250-SMTPUTF8
250 HELP
mail from: root@it-studio.pro
250 OK
rcpt to: boss@it-studio.pro
250 Accepted
data
354 Enter message, ending with "." on a line by itself
Hello there.
.
250 OK id=1cTT87-000Gm8-4j
quit
221 mail.it-studio.pro closing connection
Connection closed by foreign host.

В логах появилась следующие строки

2017-01-17 12:45:09 1cTT87-000Gm8-4j < = root@it-studio.pro H=localhost (mail.it-studio.pro) [127.0.0.1] P=esmtp S=241 from  for boss@it-studio.pro
2017-01-17 12:45:10 1cTT87-000Gm8-4j => boss@it-studio.pro R=virtual_localuser T=local_delivery
2017-01-17 12:45:10 1cTT87-000Gm8-4j Completed
2017-01-17 12:45:17 SMTP connection from localhost (mail.it-studio.pro) [127.0.0.1] closed by QUIT

Добавить комментарий