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

Поднимаем почтарь с балансировкой.

Добавлено: 01 ноя 2011, 15:54
Raven
Иногда возникает необходимость поднимать почтарь для очень большого количества ящиков, и стандартные средства тут уж как ни печально вряд-ли могут удовлетворить ваши потребности. Вот и меня постигла та же участь.

Что у меня есть:
  • 1. фаерволл (FreeBSD 7.4)
    2. 2 сервака за ним (RHEL 6.1)
    3. SAN под хранилищем всего юзерского файла (можно также юзать nfs, DRBD, fuse-sshfs)
    4. руки средней кривости
    5. мозги слегка побитые плесенью
Постараемся выжать из этого что-либо. ))))

Часть 1 - SMTP

В качестве smtp-сервера я выбрал Exim - имхо он полегче, пошустрее да и при больших нагрузках не проседает. Если кому-либо критично юзать postfix - это ваше право, но в этом случае можно пропустить эту часть и много чего из последующих. Если же кто-то решится поднять smtp на sendmail... сочувствую в общем. Данные о пользователях и пр. будем хранить в MySQL (конечно не самый лучший выбор по сравнению с PostgreSQL, но что остается делать если все "крутые" кодеры кроме него и MSSQL ничего более не знают?). MySQL у нас крутится пока на одном из хостов, этот хост также резольвится скажем как mysql.myhost.kg, MySQL прослушивает все интерфейсы на стандартном порту. Домены у нас myhost.kg и mail.myhost.kg.

Итак, ставим все что нам нужно.

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

yum install mysql-server mysql-client clamav clamd exim exim-mysql
для полного счастья удаляем postfix

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

yum remove postfix
Предположим что хранилище у нас смонтировано в /srv/data/mail на обоих серверах.
Переносим туда все с чем exim будет работать

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

export EXIM=/srv/data/mail
mkdir $EXIM/etc
mv /etc/exim/exim.conf $EXIM/etc
mkdir $EXIM/var
mkdir $EXIM/var/spool
mv /var/spool/exim $EXIM/var/spool/
mv /var/spool/mail $EXIM/var/spool/
ln -s $EXIM/var/spool/mail /var/spool/mail
ln -s $EXIM/var/spool/exim /var/spool/exim
На втором серваке:

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

export EXIM=/srv/data/mail
mkdir $EXIM/etc
rm -f /etc/exim/exim.conf
rm -rf /var/spool/exim
rm -rf /var/spool/mail
ln -s $EXIM/var/spool/mail /var/spool/mail
ln -s $EXIM/var/spool/exim /var/spool/exim
правим конфиг /srv/data/mail/etc/exim.conf

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

primary_hostname = mail.myhost.kg

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 : 192.168.50.0/24

GET_QUOTA=${lookup mysql{SELECT quota FROM users \
           WHERE login='${local_part}' AND domain='${domain}'}{${value}M}}

MAILDIR_SIZE=${eval:${sg{${sg{${readfile{/srv/data/mail/var/spool/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/pki/tls/private/exim.pem
tls_privatekey = /etc/pki/tls/private/exim.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


acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data

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

trusted_users = lighttpd
qualify_domain = myhost.kg
allow_domain_literals = false
exim_user = exim
exim_group = exim
never_users = bin
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 = 200M
smtp_accept_max = 1000
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

#Инклюдим файлик кодорый ДОЛЖЕН лежать не на хранилище, ибо у машин все же разные ипы.
.include /etc/exim/host.conf"

# Подключение к MySQL: хост/база_данных/пользователь/пароль
hide mysql_servers = mysql.myhost.kg/mail/exim/exim_password

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   = ^[./|] : ^.*[@%!] : ^.*/\\.\\./

deny message   = "All email from *.orange.fr - discarded!"
     condition = ${if match{$sender_helo_name}{.orange.fr}{yes}{no}}

deny message   = "All email from *.mdp2.net - discarded!"
     condition = ${if match{$sender_helo_name}{.mdp2.net}{yes}{no}}

deny message   = "All email from *.mail.comcast.net - discarded!"
     condition = ${if match{$sender_helo_name}{.mail.comcast.net}{yes}{no}}

deny message   = "All email from *.libero.it - discarded!"
     condition = ${if match{$sender_helo_name}{.libero.it}{yes}{no}}

deny message   = "All email from *.ono.com - discarded!"
     condition = ${if match{$sender_helo_name}{.ono.com}{yes}{no}}

deny message   = "All email from *.wanadoo.fr - discarded!"
     condition = ${if match{$sender_helo_name}{.wanadoo.fr}{yes}{no}}


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.ispalternativa.net.ua!"

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}\
                     {adsl|dialup|pool|peer|dhcp} {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


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_data:

deny     message  = This message contains a virus ($malware_name).
         demime   = *
         malware  = */defer_ok
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

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 = /srv/data/mail/var/spool/exim/$domain/$local_part/forward
  user = exim
  group = exim
  allow_filter
  no_verify
  no_expn
  check_ancestor
  file_transport = address_file
  pipe_transport = address_pipe
  reply_transport = address_reply
  condition = ${if exists{/srv/data/mail/var/spool/exim/$domain/$local_part/forward}{yes}{no}}

virtual_user_quota_defer:
  driver          = redirect
  domains         = +local_domains
  condition       = ${if and{\
                    {exists{/srv/data/mail/var/spool/exim/$domain/$local_part}}\
                    {exists{/srv/data/mail/var/spool/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


local_delivery:
  driver = appendfile
  maildir_use_size_file
  check_string = ""
  create_directory
  delivery_date_add
  directory = ${lookup mysql{SELECT \
             LOWER(CONCAT('/srv/data/mail/var/spool/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
В файлик /etc/exim/host.conf пишем:

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

smtp_banner = "$primary_hostname ESMTP srv1.myhost.kg" # Внесем ясность 
local_interfaces = 127.0.0.1 : 192.168.50.1 #192.168.50.3 соответственно для второй машины
Настраиваем MySQL и создаем базу:

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

mysqladmin password <пароль рута>
mysql -uroot -p<пароль рута>
mysql> CREATE DATABASE mail;
mysql> grant all on mail.* to 'exim'@'192.168.50.1' identified by 'exim_password';
mysql> grant all on mail.* to 'exim'@'192.168.50.3' identified by 'exim_password';
mysql> grant all on mail.* to 'exim'@'localhost' identified by 'exim_password';
Выкачиваем файл дампа

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

wget http://muff.kiev.ua/files/exim.sql
Открываем его и изменяем кодировку на utf8, ENGINE на InnoDB (так надежнее) и имя домена на свое.
Заливаем измененный дамп в муську:

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

 mysql -u exim -pexim_password mail < exim.sql
Структура БД у нас есть. Теперь пора "заполнить" ее необходимыми данными. По очереди внесу по одной записи в каждую из таблиц.

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

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 `mail`.`aliases` (`local_part`, `domain`, `recipients`) VALUES ('root', 'myhost.kg', 'raven@myhost.kg');
Query OK, 1 row affected (0.00 sec)

 mysql> INSERT INTO `mail`.`blacklist` (`senders`, `when_added`) VALUES ('helenaVIP@mail.ru', CURDATE());
Query OK, 1 row affected (0.00 sec)

 mysql> INSERT INTO `mail`.`blacklist_host` (`senders`, `when_add`) VALUES ('12.134.36.100', CURDATE());
Query OK, 1 row affected (0.01 sec)

 mysql> INSERT INTO `mail`.`domains` (`domain`, `type`) VALUES ('myhost.kg', 'LOCAL');
Query OK, 1 row affected (0.00 sec)

 mysql> INSERT INTO `mail`.`users` (`login`, `name`, `password`, `uid`, `gid`, `domain`,  `quota`, `status`, `smtp_auth`) VALUES ('raven', 'Raven. System administrator', ENCRYPT('password-here'), '26', '6', 'myhost.kg', '150', '1', '1');
Query OK, 1 row affected (0.00 sec)

 mysql> INSERT INTO `mail`.`users` (`login`, `name`, `password`, `uid`, `gid`, `domain`,  `quota`, `status`, `smtp_auth`) VALUES ('test', 'Test User', ENCRYPT('password-here'), '27', '6', 'myhost.kg', '150', '1', '1');
Query OK, 1 row affected (0.00 sec)

 mysql> INSERT INTO `mail`.`whitelist` (`senders`) VALUES ('admin@sysadmins.el.kg');
Query OK, 1 row affected (0.00 sec)
Перенесем сертификат (он должен быть идентичным для обоих серверов):

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

scp /etc/pki/tls/private/exim.pem root@192.168.50.1:/etc/pki/tls/private/
root@192.168.50.1's password:
exim.pem                                                                                                                                                                           100% 1704     1.7KB/s   00:00
Стартуем демоны на обоих серверах. Если что-либо не так - смотрите логи в /var/log/exim

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

service clamd start
service exim start
chkconfig exim on
chkconfig clamd on
Проверим работу smtp на обоих серверах:

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

 telnet localhost 25
Trying 127.0.0.1...                    
Connected to localhost.                
Escape character is '^]'.              
220 mail.myhost.kg ESMTP srv1.myhost.kg        
ehlo mail.myhost.kg                  
250-mail.myhost.kg Hello mail.myhost.kg [127.0.0.1]
250-SIZE 209715200                                     
250-PIPELINING                                         
250-AUTH LOGIN PLAIN                                   
250-STARTTLS                                           
250 HELP                                               
mail from: root@myhost.kg                            
250 OK                                                 
rcpt to: test@myhost.kg                              
250 Accepted                                           
data                                                   
354 Enter message, ending with "." on a line by itself 
raven send test message                                
.                                                      
250 OK id=1RL9wZ-0004CM-10                             
quit                                                   
221 mail.myhost.kg closing connection                
Connection closed by foreign host.
Все ОК - идем пить пиво :)

Re: Поднимаем почтарь с балансировкой.

Добавлено: 01 ноя 2011, 18:09
Raven

Часть 2 - Балансировщик

Много было вариаций на тему как же настроить балансировку запросов между 2 серваками... пока решил просто (на шлюзе):

Скачиваем и собираем программку Balance

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

fetch http://www.inlab.de/balance-3.54.tar.gz
tar -xvz balance-3.54.tar.gz
cd balance-3.54
make
И запускаем ее с параметрами:

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

./balance -b <белый_ип> smtp 192.168.50.3 192.168.50.1
Проверяем:

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

telnet mail.myhost.kg 25
Trying 127.0.0.1...                    
Connected to localhost.                
Escape character is '^]'.              
220 mail.myhost.kg ESMTP srv3.myhost.kg        
ehlo mail.myhost.kg                  
250-mail.myhost.kg Hello mail.myhost.kg [ип]
250-SIZE 209715200                                     
250-PIPELINING                                         
250-AUTH LOGIN PLAIN                                   
250-STARTTLS                                           
250 HELP                                               
mail from: root@myhost.kg                            
250 OK                                                 
rcpt to: test@myhost.kg                              
250 Accepted                                           
data                                                   
354 Enter message, ending with "." on a line by itself 
raven send test message                                
.                                                      
250 OK id=1RL9wZ-0004CM-10                             
quit                                                   
221 mail.myhost.kg closing connection                
Connection closed by foreign host.
Копируем бинарник куда следует

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

cp balance /usr/local/bin
Поскольку вручную все это дело запускать каждый раз в лом пишем скрипт для автозапуска:

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

#!/bin/sh                                                                                                                            

# PROVIDE: balance
# REQUIRE: DAEMON
# KEYWORD: shutdown

#######
#
# Add the following lines to /etc/rc.conf to enable balance:
#
# balance_enable (bool):        default: "NO"
#                               Set to "YES" to enable balance
# balance_ip (str):        default: 127.0.0.1
#                               Set IP address to bind
# balance_ip (str):         default: smtp
#                               Set port to bind balance
# balance_back (str):         default: 192.168.50.1 192.168.50.3
#                               Set balance backends
# balance_flags (str):          default: Autogenerated using pidfile and config options
#                               Set to override with your own options
#

. /etc/rc.subr

name="balance"
rcvar=`set_rcvar`
command="/usr/local/bin/balance"

# Load Configs/Set Defaults
load_rc_config $name
: ${balance_enable:="NO"}
: ${balance_ip="192.168.50.33"}
: ${balance_port="pop3"}
: ${balance_back="192.168.50.1 192.168.50.3"}
: ${balance_flags="-b ${balance_ip} ${balance_port} ${balance_nodes}"}


run_rc_command "$1"
Закидываем его в /usr/local/etc/rc.d и прописываем в /etc/rc.conf

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

balance_enable="YES"
balance_ip="<белый ип>"
balance_port="smtp"
balance_nodes="192.168.50.1 192.168.50.3"
Все ОК - снова пьем пиво *NYAM*