Несмотря на кажущуюся простоту 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 fromfor 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