Версия для печати

Архив документации на OpenNet.ru / Раздел "Настройка почты (sendmail, postfix, qmail)" (Многостраничная версия)

Установка почтового сервера на базе Exim под FreeBSD


Автор: Олег Островерх aka Wombat
Оригинал: wombat.stapravda.ru

Оглавление

  1. Установка и настройка Exim
  2. Устанавливаем IMAP сервер Dovecot
  3. Устанавливаем антивирусный пакет ClamAV
  4. Устанавливаем Spamassassin, для фильтрации спама

Устанавливаем СlamAV

Стоит ли тебе рассказывать, что такое вирусы? Я думаю, что ты про эти гадости слышал не раз по телевизору. И последствия тебе тоже известны.

Попробуем защитить себя и свою сеть от проникновения вирусов через наш почтовый сервер. Для этого мы задействуем Clam AntiVirus (ClamAV).

Мы можем сразу ринуться в бой, но прежде установим две дополнительные библиотеки (zlib и GNU MP), без которых ClamAV не хочет устанавливаться.

Последнюю версию zlib (на момент написания - 1.2.2) мы утянем с сайта www.zlib.net, распакуем и установим.

$ cd $HOME/install
$ links www.zlib.net
$ tar xzfv zlib-1.2.2.tar.gz
$ ./configure
$ make
$ su
# make install

Библиотеку GNU MP мы установим из портов.

# cd /usr/ports/math/libgmp4
# make
# make install
# exit

Вот теперь можно и в бой. Забираем последнюю версию нашего антивируса (на момент написания - 0.83) с сайта www.clamav.net

$ cd $HOME/install
$ links www.clamav.net
$ tar xzfv clamav-0.83.tar.gz
$ cd clamav-0.83

Для конфигурирования создадим скрипт.

$ ee config.sh
### config.sh for clamav ###
./configure \
--prefix=/usr/local/clamav \
--with-user=exim \
--with-group=mail

Что делать дальше, догадываешся?

$ sh config.sh
$ make
$ su
# make install

Теперь создадим конфиг-файл для нашего антивирусного демона.

# ee /usr/local/clamav/etc/clamd.conf
### /usr/local/clamav/etc/clamd.conf ###
LogFile /usr/local/clamav/var/log/clamd.log
LogTime
PidFile /usr/local/clamav/var/run/clamd.pid
TemporaryDirectory /usr/local/clamav/var/tmp
DatabaseDirectory /usr/local/clamav/share/clamav
LocalSocket /usr/local/clamav/var/tmp/clamd.sock
FixStaleSocket
MaxConnectionQueueLength 150
MaxThreads 150
ReadTimeout 30
MaxDirectoryRecursion 20
User exim
ScanOLE2
ScanArchive
ArchiveMaxFileSize 10M
ArchiveMaxRecursion 8
ArchiveMaxFiles 1000
ArchiveMaxCompressionRatio 200

И конфиг-файл для программы обновления.

# ee /usr/local/clamav/etc/freshclam.conf
### /usr/local/clamav/etc/freshclam.conf ###
DatabaseDirectory /usr/local/clamav/share/clamav
UpdateLogFile /usr/local/clamav/var/log/freshclam.log
DatabaseOwner exim
DNSDatabaseInfo current.cvd.clamav.net
DatabaseMirror database.clamav.net
MaxAttempts 5
Checks 24

Создадим нобходимые для работы антивируса каталоги и разберемся с правами.

# mkdir -p /usr/local/clamav/var/log \
? /usr/local/clamav/var/run /usr/local/clamav/var/tmp
# chown -R exim:mail /usr/local/clamav/share /usr/local/clamav/var

Напишем скрипт для запуска нашего демона при старте системы и присвоим ему исполняемый бит.

# ee /usr/local/etc/rc.d/00clamav.sh
#!/bin/sh

case "$1" in
    start)
        echo -n "Starting ClamAV daemon..."
        /usr/local/clamav/sbin/clamd
    ;;
    stop)
        echo -n "Shutting down ClamAV daemon..."
        kill `cat /usr/local/clamav/var/run/clamd.pid | head -1`
    ;;
    restart)
        echo -n "Restarting ClamAV daemon..."
        kill `cat /usr/local/clamav/var/run/clamd.pid | head -1`
        sleep 1
        /usr/local/clamav/sbin/clamd
    ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
    ;;
esac

chmod +x /usr/local/etc/rc.d/00clamav.sh

Теперь пришло время запустить наш антивирус.

# /usr/local/etc/rc.d/00clamav.sh start

Вирусописатели не спят и все время чавой-то ваяют. Вот и мы будем ваять. Настроим обновление антивирусных баз. Тебе опять придется столкнуться с редактором vi. Неужели ты до сих про с ним не познакомился?

# crontab -e -u exim

Нажимаем клавишу o и вводим строку:

42  *  *  *  *  /usr/local/clamav/bin/freshclam --quiet

По окончании ввода нажимаем ESC, а не ENTER и затем последовательно :wq. Теперь на 42-й минуте каждого часа мы будем получать обновление наших антивирусных баз. Для меня этого вполне достаточно. Во время вирусных эпидемий (давненько не было) рекомендую изменить первую цифру 42 на */15. И обновления будут происходить каждые 15 минут.

Антивирус мы установили и даже запустили. Обновления настроили. Теперь пришло время связать его с нашим почтовым сервером.

Для этого нужно внести изменения в конфигурационный файл Exim. Добавить в раздел MAIN CONFIGURATION SETTINGS строки:

acl_smtp_data = acl_check_content
av_scanner = clamd:/usr/local/clamav/var/tmp/clamd.sock

И в конец раздела ACL CONFIGURATION:

acl_check_content:

  deny  message = Virus found ($malware_name)
        malware = *

accept

Окончательный вид файла конфигураци /usr/local/exim/configure будет следующий:

(внесенные изменения выделены полужирным шрифтом)


######################################################################
#                  Runtime configuration file for Exim               #
######################################################################
#######################################################################
#                    MAIN CONFIGURATION SETTINGS                     #
######################################################################

primary_hostname = mydomain.ru
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   relay_from_hosts = 127.0.0.1
auth_advertise_hosts = *
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_protocol_error \
        -queue_run
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_mime = acl_check_mime
acl_smtp_data = acl_check_content
av_scanner = clamd:/usr/local/clamav/var/tmp/clamd.sock
qualify_domain = mydomain.ru
allow_domain_literals = false
never_users = root
host_lookup = *
rfc1413_hosts = *
rfc1413_query_timeout = 0s
ignore_bounce_errors_after = 30m
timeout_frozen_after = 3d
freeze_tell = postmaster
message_size_limit = 10M
smtp_accept_max = 100
smtp_accept_max_per_connection = 5
#smtp_connect_backlog = 50
smtp_accept_max_per_host = 2
split_spool_directory = true
remote_max_parallel = 15
smtp_banner = "Welcome on our mail server!\n\
    This system does not accept Unsolicited \
    Commercial Email\nand will blacklist \
    offenders via our spam processor.\nHave a \
    nice day!\n\n${primary_hostname} ESMTP"

hide mysql_servers = localhost/exim/sqlmail/my_password

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

acl_check_rcpt:

  accept  hosts = :

  deny    domains       = +local_domains
          local_parts   = ^[.] : ^.*[@%!/|]

  deny    domains       = !+local_domains
          local_parts   = ^[./|] : ^.*[@%!] : ^.*/\\.\\./

  accept  local_parts   = postmaster
          domains       = +local_domains

  require verify        = sender

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

  deny    message       = Go Away! You are spammer.
          condition     = ${if match{$sender_host_name} \
                          {bezeqint\\.net|net\\.il|dialup|dsl|pool|peer|dhcp} \
                          {yes}{no}}

  deny    message       = rejected because \
  $sender_host_address is in a black list at $dnslist_domain\n$dnslist_text
          log_message   = found in $dnslist_domain
          dnslists      = relays.ordb.org
  deny    message       = message from \
  $sender_host_address rejected - see http://njabl.org/
          log_message   = found in $dnslist_domain
          dnslists      = dnsbl.njabl.org
  deny    message       = rejected because \
  $sender_host_address for bad WHOIS info, see http://www.rfc-ignorant.org/
          log_message   = found in $dnslist_domain
          dnslists      = ipwhois.rfc-ignorant.org
  deny    message       = rejected because $sender_host_address \
  is in a black list at $dnslist_domain\n$dnslist_text
          log_message   = found in $dnslist_domain
          dnslists      = dialups.mail-abuse.org
  deny    message       = rejected because $sender_host_address \
  is in a black list at $dnslist_domain\n$dnslist_text
          log_message   = found in $dnslist_domain
          dnslists      = list.dsbl.org
  deny    message       = Spam blocked see: \
  http://www.spamcop.net/w3m?action=checkblock&ip=$sender_host_address
          log_message   = found in $dnslist_domain
          dnslists      = bl.spamcop.net
  deny    message       = rejected, $sender_host_address \
  Open Proxy, see: $dnslist_domain\n$dnslist_text
          log_message   = found in $dnslist_domain
          dnslists      = dnsbl.void.ru

  accept  domains       = +local_domains
          endpass
          message       = unknown user
          verify        = recipient

  accept  domains       = +relay_to_domains
          endpass
          message       = unrouteable address
          verify        = recipient

  accept  hosts         = +relay_from_hosts
  accept  authenticated = *
  deny    message       = relay not permitted

acl_check_mime:

  warn decode = default

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

  deny message = Sorry, noone speaks chinese here
       condition = ${if eq{$mime_charset}{gb2312}{1}{0}}

accept

acl_check_content:

  deny  message = Virus found ($malware_name)
        malware = *

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

userforward:
  driver = redirect
  allow_fail
  allow_defer
  data = ${lookup mysql{SELECT recipients FROM userforward \
         WHERE local_part='${local_part}' AND domain='${domain}'}}

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

######################################################################
#                      TRANSPORTS CONFIGURATION                      #
######################################################################
#                       ORDER DOES NOT MATTER                        #
#     Only one appropriate transport is called for each delivery.    #
######################################################################

begin transports

remote_smtp:
  driver = smtp

local_delivery:
  driver = appendfile
  check_string = ""
  create_directory
  delivery_date_add
  directory = /var/mail/$domain/$local_part
  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 = 75%
  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

######################################################################
#                      RETRY CONFIGURATION                           #
######################################################################

begin retry

*                      quota
*                      *           F,2h,15m; G,16h,1h,1.5; F,4d,6h


######################################################################
#                      REWRITE CONFIGURATION                         #
######################################################################

begin rewrite

######################################################################
#                   AUTHENTICATION CONFIGURATION                     #
######################################################################

begin authenticators

auth_plain:
  driver = plaintext
  public_name = PLAIN
  server_condition = ${lookup mysql{SELECT login FROM users \
                        WHERE login = '${quote_mysql:${local_part:$2}}' \
                        AND domain = '${quote_mysql:${domain:$2}}' \
                        AND decrypt = '${quote_mysql:$3}' \
                        AND status = '1'}{yes}{no}}
  server_prompts = :
server_set_id = $2

auth_login:
  driver = plaintext
  public_name = LOGIN
  server_condition = ${lookup mysql{SELECT login FROM users \
                        WHERE login = '${quote_mysql:${local_part:$1}}' \
                        AND domain = '${quote_mysql:${domain:$1}}' \
                        AND decrypt = '${quote_mysql:$2}' \
                        AND status = '1'}{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 decrypt FROM users \
                        WHERE login = '${quote_mysql:${local_part:$1}}' \
                        AND domain = '${quote_mysql:${domain:$1}}' \
                        AND status = '1'}{$value}fail}
server_set_id = $1

# End of Exim configuration file

После внесения изменений нам остается только перезапустить Exim

# /usr/local/etc/rc.d/exim.sh restart

и наслаждаться результатами нашего труда, заглядывая в логи.


Устанавливаем Dovecot

До последнего врeмени в качестве pop-сервера я использовал старый добрый Qpopper. Пока не возникла новая задача: web-интерфейс для почтового сервера (вот любит начальство кататься по стране). И опять благодаря Ginger мои взоры были направлены на интересный secure IMAP server - Dovecot. IMAP-сервер, рассчитанный на максимальную безопасность и надежность, способный обслуживать клиентов по протоколам imap, imaps, pop3, pop3s.

Ты заинтригован? Тогда вперед. Забираем последнюю версию с сайта http://dovecot.org (на момент написания - версия 0.99.14).

$ cd $HOME/install
$ links http://dovecot.org
$ tar xzfv dovecot-0.99.14.tar.gz
$ cd dovecot-0.99.14

Для конфигурации создадим файл config.sh

$ ee config.sh

следующего содержания:


./configure \
--with-ssl=openssl \
--with-mysql \
--with-pop3d \
--without-passwd \
--without-passwd-file \
--without-shadow \
--without-pam \
--without-ldap \
--without-vpopmail \
--without-static-userdb

Теперь скомпилируем и установим наш Dovecot.

$ sh config.sh
$ make
$ su
# make install

Теперь для работы Dovecot создадим два конфигурационных файла: /usr/local/etc/dovecot.conf и /etc/dovecot-mysql.conf.

# ee /usr/local/etc/dovecot.conf
base_dir = /var/run/dovecot/
protocols =  imaps pop3s imap pop3
ssl_cert_file = /etc/ssl/certs/mail.pem
ssl_key_file = /etc/ssl/certs/mail.pem
login = imap
login_executable = /usr/local/libexec/dovecot/imap-login
login_user = exim
login = pop3
login_executable = /usr/local/libexec/dovecot/pop3-login
mail_extra_groups = mail
default_mail_env = maildir:/var/mail/%d/%n
imap_executable = /usr/local/libexec/dovecot/imap
pop3_executable = /usr/local/libexec/dovecot/pop3
auth = default
auth_mechanisms = plain
auth_userdb = mysql /etc/dovecot-mysql.conf
auth_passdb = mysql /etc/dovecot-mysql.conf
first_valid_uid = 1
auth_user = exim
auth_verbose = yes

# ee /etc/dovecot-mysql.conf
db_unix_socket = /tmp/mysql.sock
db = exim
db_user = sqlmail
db_passwd = my_password
db_client_flags = 0
default_pass_scheme = CRYPT
password_query = SELECT password FROM users WHERE login = '%n' AND domain = '%d' AND status = '1'
user_query = SELECT uid, gid FROM users WHERE login = '%n' AND domain = '%d'

Изменим права на наши конфигурационные файлы:

# chown root:mail /etc/dovecot-mysql.conf /usr/local/etc/dovecot.conf
# chmod 640 /etc/dovecot-mysql.conf /usr/local/etc/dovecot.conf

И создадим скрипт для запуска нашего IMAP-POP-сервера:

# ee /usr/local/etc/rc.d/dovecot.sh
#!/bin/sh

prog=/usr/local/sbin/dovecot
conf=/usr/local/etc/dovecot.conf

case "$1" in
  start)
        [ -x ${prog} ] && ${prog} -c ${conf}
        echo -n " dovecot"
        ;;
  stop)
        kill -TERM `cat /var/run/dovecot/master.pid`
        echo -n " "
        ;;
  restart)
        $0 stop
        $0 start
        ;;
  reload)
        kill -HUP `cat /var/run/dovecot/master.pid`
        ;;
  *)
        echo "Usage: `basename $0` {start|stop|restart|reload}" >&2
        exit 1
esac
exit 0

# chmod +x /usr/local/etc/rc.d/dovecot.sh

Осталось его запустить.

# /usr/local/etc/rc.d/dovecot.sh start
# exit

Будем проверять, как он работает?

Проверяем работу POP3.

$ telnet localhost 110
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.ru.
Escape character is '^]'.
+OK dovecot ready.

user admin@mydomain.ru
+OK

pass my_password
+OK Logged in.

list
+OK 0 messages:
.

quit
+OK Logging out.
Connection closed by foreign host.

Если ошибок нет (откуда им взяться), идем дальше. Проверяем работу IMAP.

$ telnet localhost 143
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.ru.
Escape character is '^]'.
* OK dovecot ready.

* LOGIN admin@mydomain.ru my_password
* OK Logged in.

* STATUS Inbox (MESSAGES)
* STATUS "Inbox" (MESSAGES 0)
* OK Status completed.

* LOGOUT
* BYE Logging out
* OK Logout completed.
Connection closed by foreign host.

Если проблем не возникло, проверяем защищенное сединение POP3S.

$ openssl
OpenSSL> s_client -host localhost -port 995
CONNECTED(00000003)
depth=0 /C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster
@mydomain.ru
verify error:num=18:self signed certificate
verify return:1
depth=0 /C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster
@mydomain.ru
verify return:1
---
Certificate chain
 0 s:/C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster@my
domain.ru
   i:/C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster@my
domain.ru
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIEADCCA2mgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCUlUx
MDYxNTQ5MzhaMIG3MQswCQYDVQQGEwJSVTEcMBoGA1UECBMTU3RhdnJvcG9sIFRl
bDE3MDUGA1UEChQuRWRpdG9yaWFsIG9mIE5ld3NwYXBlciAiU3RhdnJvcG9sc2th
eWEgUHJhdmRhIjEVMBMGA1UEAxMMc3RhcHJhdmRhLnJ1MSYwJAYJKoZIhvcNAQkB
Fhdwb3N0bWFzdGVyQHN0YXByYXZkYS5ydTAeFw0wNTA0MjIxNTQ5MzhaFw0zMjA5
MDYxNTQ5MzhaMIG3MQswCQYDVQQGEwJSVTEcMBoGA1UECBMTU3RhdnJvcG9sIFRl
cnJpdG9yeTESMBAGA1UEBxMJU3RhdnJvcG9sMTcwNQYDVQQKFC5FZGl0b3JpYWwg
b2YgTmV3c3BhcGVyICJTdGF2cm9wb2xza2F5YSBQcmF2ZGEiMRUwEwYDVQQDEwxz
MDYxNTQ5MzhaMIG3MQswCQYDVQQGEwJSVTEcMBoGA1UECBMTU3RhdnJvcG9sIFRl
LnJ1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNFT87/U4Y/isxCQR2aD0n
LgJ9DvOwWBFs72R1ciYvOUOQVWGAhMCeGjVkiPtysjFtRRdgKjGvKrKmx9C3pH3w
ZN5fXFcZBqUtiLzQ0VKcdw3utAvRM4gzICSlktpjPtxfoYYSC4CUHA1/0+WDC7HO
HVyM7oBL+pRZyQT0A/8k7QIDAQABo4IBGDCCARQwHQYDVR0OBBYEFIVHLfnzpOO5
ub9oTTvksXmf9F7gMIHkBgNVHSMEgdwwgdmAFIVHLfnzpOO5ub9oTTvksXmf9F7g
oYG9pIG6MIG3MQswCQYDVQQGEwJSVTEcMBoGA1UECBMTU3RhdnJvcG9sIFRlcnJp
dG9yeTESMBAGA1UEBxMJU3RhdnJvcG9sMTcwNQYDVQQKFC5FZGl0b3JpYWwgb2Yg
HVyM7oBL+pRZyQT0A/8k7QIDAQABo4IBGDCCARQwHQYDVR0OBBYEFIVHLfnzpOO5
MDYxNTQ5MzhaMIG3MQswCQYDVQQGEwJSVTEcMBoGA1UECBMTU3RhdnJvcG9sIFRl
ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAA4EghSwza0gbNcik
HVyM7oBL+pRZyQT0A/8k7QIDAQABo4IBGDCCARQwHQYDVR0OBBYEFIVHLfnzpOO5
eWEyOMmOGQbx/++XLU35CFrdiqEwdZTVfZscoefyhp9shsG1Dw8zbVK+im7wmmA1
zokwLdwUQZGVBRTefUivYOSOJ9c=
-----END CERTIFICATE-----
subject=/C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster
@mydomain.ru
issuer=/C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster@
mydomain.ru
---
No client certificate CA names sent
---
SSL handshake has read 1190 bytes and written 340 bytes
---
New, TLSv1/SSLv3, Cipher is AES256-SHA
Server public key is 1024 bit
SSL-Session:
    Protocol  : TLSv1
    Cipher    : AES256-SHA
    Session-ID: FF415922B742484BA13B84FAD575C528B3549B742484BA13AB1393C4AB3D8010

    Session-ID-ctx:
    Master-Key: 8A39B36BFDB180ED55F829DBC420D675F0ECAF1B07FBBED3A1632A3AA8ECCE96
4CAACF8CDAE5F829DBC420D3F5AE9BA8
    Key-Arg   : None
    Start Time: 1114190806
    Timeout   : 300 (sec)
    Verify return code: 18 (self signed certificate)
---
+OK dovecot ready.

Соединение получили. Если у тебя есть желание, то можешь проделать операции, описанные при проверке POP3, а мне лень. Но не спеши выходить из openssl. Сразу проверим IMAPS - защищенный IMAP

quit
OpenSSL> s_client -host localhost -port 993
CONNECTED(00000003)
depth=0 /C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster
@mydomain.ru
verify error:num=18:self signed certificate
verify return:1
depth=0 /C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster
@mydomain.ru
verify return:1
---
Certificate chain
 0 s:/C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster@my
domain.ru
   i:/C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster@my
domain.ru
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIEADCCA2mgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCUlUx
MDYxNTQ5MzhaMIG3MQswCQYDVQQGEwJSVTEcMBoGA1UECBMTU3RhdnJvcG9sIFRl
bDE3MDUGA1UEChQuRWRpdG9yaWFsIG9mIE5ld3NwYXBlciAiU3RhdnJvcG9sc2th
eWEgUHJhdmRhIjEVMBMGA1UEAxMMc3RhcHJhdmRhLnJ1MSYwJAYJKoZIhvcNAQkB
Fhdwb3N0bWFzdGVyQHN0YXByYXZkYS5ydTAeFw0wNTA0MjIxNTQ5MzhaFw0zMjA5
MDYxNTQ5MzhaMIG3MQswCQYDVQQGEwJSVTEcMBoGA1UECBMTU3RhdnJvcG9sIFRl
cnJpdG9yeTESMBAGA1UEBxMJU3RhdnJvcG9sMTcwNQYDVQQKFC5FZGl0b3JpYWwg
b2YgTmV3c3BhcGVyICJTdGF2cm9wb2xza2F5YSBQcmF2ZGEiMRUwEwYDVQQDEwxz
MDYxNTQ5MzhaMIG3MQswCQYDVQQGEwJSVTEcMBoGA1UECBMTU3RhdnJvcG9sIFRl
LnJ1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNFT87/U4Y/isxCQR2aD0n
LgJ9DvOwWBFs72R1ciYvOUOQVWGAhMCeGjVkiPtysjFtRRdgKjGvKrKmx9C3pH3w
ZN5fXFcZBqUtiLzQ0VKcdw3utAvRM4gzICSlktpjPtxfoYYSC4CUHA1/0+WDC7HO
HVyM7oBL+pRZyQT0A/8k7QIDAQABo4IBGDCCARQwHQYDVR0OBBYEFIVHLfnzpOO5
ub9oTTvksXmf9F7gMIHkBgNVHSMEgdwwgdmAFIVHLfnzpOO5ub9oTTvksXmf9F7g
oYG9pIG6MIG3MQswCQYDVQQGEwJSVTEcMBoGA1UECBMTU3RhdnJvcG9sIFRlcnJp
dG9yeTESMBAGA1UEBxMJU3RhdnJvcG9sMTcwNQYDVQQKFC5FZGl0b3JpYWwgb2Yg
HVyM7oBL+pRZyQT0A/8k7QIDAQABo4IBGDCCARQwHQYDVR0OBBYEFIVHLfnzpOO5
MDYxNTQ5MzhaMIG3MQswCQYDVQQGEwJSVTEcMBoGA1UECBMTU3RhdnJvcG9sIFRl
ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAA4EghSwza0gbNcik
HVyM7oBL+pRZyQT0A/8k7QIDAQABo4IBGDCCARQwHQYDVR0OBBYEFIVHLfnzpOO5
eWEyOMmOGQbx/++XLU35CFrdiqEwdZTVfZscoefyhp9shsG1Dw8zbVK+im7wmmA1
zokwLdwUQZGVBRTefUivYOSOJ9c=
-----END CERTIFICATE-----
subject=/C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster
@mydomain.ru
issuer=/C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster@
mydomain.ru
---
No client certificate CA names sent
---
SSL handshake has read 1190 bytes and written 340 bytes
---
New, TLSv1/SSLv3, Cipher is AES256-SHA
Server public key is 1024 bit
SSL-Session:
    Protocol  : TLSv1
    Cipher    : AES256-SHA
    Session-ID: FF415922B742484BA13B84FAD575C528B3549B742484BA13AB1393C4AB3D8010

    Session-ID-ctx:
    Master-Key: 8A39B36BFDB180ED55F829DBC420D675F0ECAF1B07FBBED3A1632A3AA8ECCE96
4CAACF8CDAE5F829DBC420D3F5AE9BA8
    Key-Arg   : None
    Start Time: 1114190806
    Timeout   : 300 (sec)
    Verify return code: 18 (self signed certificate)
---
* OK dovecot ready.

И здесь получили соединение.

* LOGOUT
OpenSSL> quit

Все у нас прекрасно работает. Но это не все наши героические поступки. Далее мы приступим к построению оборонительных сооружений для нашего почтового сервера. Начнем с антивируса.


Exim и прочие приблуды к нему

Оригинал статьи, написанный Ginger (еще раз скажем ей большое спасибо) и послуживший основой для дальнейшего творчества, можно найти здесь. Я лишь внес небольшие изменения и от себя добавил описания по установке антивируса ClamAV и антиспамовского фильтра Spamassassin.

Наш почтовый сервер поддерживает виртуальные аккаунты, используя СУБД MySQL (как устанавить MySQL, я рассказал здесь), SMTP-аутентификацию и защищенное соединение TLS/SSL.

Для использования TLS/SSL создадим сертификат. Для этого выполним следующее:

$ su
# mkdir -p /etc/ssl/certs
# cd /etc/ssl/certs
# openssl req -x509 -newkey rsa:1024 -keyout mail.pem -out mail.pem \
? -days 9999 -nodes

Далее от нас потребуют заполнить некоторые поля:

Country Name (2 letter code) [CA]:
State or Province Name (full name) [Quebec]:
Locality Name (eg, city) [Montreal]:
Organization Name (eg, company) [Open Network Architecture]:
Organizational Unit Name (eg, section) [Internet Department]:
Common Name (eg, YOUR name) []:
Email Address []:

Заполняй, как твоей душе угодно (т.к. ты же не собираешься платить деньги за сертификат), кроме строки Common Name (eg, YOUR name) [ ]. Здесь нужно вписать имя нашего сервера:

Common Name (eg, YOUR name) []: mydomain.ru

В каталоге /etc/ssl/certs появился файл сертификата mail.pem, который в дальнейшем мы будем использовать для безопасного соединения с нашим почтовым сервером. Произведем над этим файлом еще две оперции:

# chmod 440 /etc/ssl/certs/mail.pem
# chgrp mail /etc/ssl/certs/mail.pem

С сертификатом покончено, теперь займемся непосредственно Exim.

Для работы Exim нам понадобится непривилегированный пользователь, принадлежащий группе mail. Создадим его.

# pw useradd exim -c "Exim" -d /var/spool/mqueue -s /sbin/nologin -g mail
# exit
$ cat /etc/passwd | grep exim
exim:*:1003:6:Exim:/var/spool/mqueue/:/sbin/nologin

Последней командой мы выясняем, что аккаунт exim имеет uid=1003, а gid=6. Эти данные нам потребуются при конфигурации.

Заберем последнюю версию Exim (на момент написания - 4.50) с сайта www.exim.org. Не следует брать более ранние версии. Это обусловлено тем, что, начиная с версии 4.50, ранее отдельно существующий патч exiscan-acl интегрирован в исходный код.

$ cd $HOME/install
$ links www.exim.org
$ tar xzfv exim-4.50.tar.gz
$ cd exim-4.50
$ cp src/EDITME Local/Makefile

Отредактируем Local/Makefile

$ ee Local/Makefile

изменив

BIN_DIRECTORY=/usr/exim/bin

на

BIN_DIRECTORY=/usr/local/exim/bin

Здесь мы указываем путь установки исполняемых файлов Exim.

CONFIGURE_FILE=/usr/exim/configure

на

CONFIGURE_FILE=/usr/local/exim/configure

Здесь мы указываем имя и путь к файлу конфигурации.

EXIM_USER=

на

EXIM_USER=1003
#EXIM_GROUP=

на

EXIM_GROUP=6

Помнишь я говорил об uid и gid? Вот сейчас они нам понадобятся. Укажем числовые идентификаторы пользователя и группы, от имени которых будет работать Exim.

SPOOL_DIRECTORY=/var/spool/exim

на

SPOOL_DIRECTORY=/var/spool/mqueue

Здесь мы указываем имя spool-директории.

#SUPPORT_MAILDIR=yes

на

SUPPORT_MAILDIR=yes

Здесь мы включаем поддержку Maildir.

#LOOKUP_MYSQL=yes

на

LOOKUP_MYSQL=yes

Здесь мы включаем поддержку MySQL.

Для того чтобы узнать путь к файлам библиотек и заголовков MySQL, выполним:

$ /usr/local/mysql/bin/mysql_config
Usage: /usr/local/mysql/bin/mysql_config [OPTIONS]
Options:
--cflags         [-I/usr/local/mysql/include/mysql -fomit-frame-pointer]
--include        [-I/usr/local/mysql/include/mysql]
--libs           [-L/usr/local/mysql/lib/mysql -lmysqlclient -lz -lcrypt -lm]
--libs_r         [-L/usr/local/mysql/lib/mysql -lmysqlclient_r -lz -lcrypt -lm
  -lpthread]
--socket         [/tmp/mysql.sock]
--port           [3306]
--version        [4.1.10a]
--libmysqld-libs [-L/usr/local/mysql/lib/mysql -lmysqld -lcrypt -lm  -lpthread]

Полученные значения подставляем в строках ниже...

# LOOKUP_INCLUDE=-I /usr/local/ldap/include -I /usr/local/mysql/include
# LOOKUP_LIBS=-L/usr/local/lib -lldap -llber -lmysqlclient -lpq

на

LOOKUP_INCLUDE=-I /usr/local/mysql/include/mysql 
LOOKUP_LIBS=-L/usr/local/mysql/lib/mysql -lmysqlclient -lz -lcrypt -lm
EXIM_MONITOR=eximon.bin

на

#EXIM_MONITOR=eximon.bin

Здесь мы выключаем поддержку Exim Monitor.

Для работы Exim Monitor требуется наличие X11, нами это не используется.

#WITH_CONTENT_SCAN=yes

на

WITH_CONTENT_SCAN=yes
#WITH_OLD_DEMIME=yes

на

WITH_OLD_DEMIME=yes

Здесь мы включаем опции, которые помогут совместной работе Exim с ClamAV и Spamassassin. Это те самые опции, которых не было до версии 4.50. Спасал патч exiscan-acl.

# AUTH_CRAM_MD5=yes 
# AUTH_PLAINTEXT=yes

на

AUTH_CRAM_MD5=yes 
AUTH_PLAINTEXT=yes

Здесь мы включаем поддержку SMTP-аутентификации PLAIN и CRAM-MD5.

# SUPPORT_TLS=yes

на

SUPPORT_TLS=yes

Здесь мы включаем поддержку TLS/SSL для обеспечения защищенного соединения.

# TLS_LIBS=-lssl -lcrypto

на

TLS_LIBS=-lssl -lcrypto

Здесь мы указываем имена используемых библиотек для TLS/SSL.

# LOG_FILE_PATH=/var/log/exim_%slog

на

LOG_FILE_PATH=/var/log/exim/exim_%slog

Сложим лог-файлы в отдельный каталог.

EXICYCLOG_MAX=10

на

EXICYCLOG_MAX=20

Количество сохраненных лог-файлов я увеличиваю до 20 на случай разбора полетов с начальством: "Где мое письмо, которое я отправил две недели назад?"

# EXIM_PERL=perl.o

на

EXIM_PERL=perl.o

Здесь мы включаем поддержку perl, чтобы обеспечить использование перл-совместимых регулярных выражений и т.д...

# CHOWN_COMMAND=/usr/bin/chown

на

CHOWN_COMMAND=/usr/sbin/chown

Здесь мы указываем правильный путь к команде chown.

# SUPPORT_MOVE_FROZEN_MESSAGES=yes

на

SUPPORT_MOVE_FROZEN_MESSAGES=yes

Здесь мы включаем поддержку автоматического перемещения "frozen" сообщений из каталогов input и msglog в каталоги Finput и Fmsglog основного спула.

Сохраняем изменения. Осталось скомпилировать и установить Exim.

$ make
$ su
# make install

После того как установка завершена, нужно создать символические ссылки, для этого проделаем следующее:

# ln -fs /usr/local/exim/bin/exim /usr/lib/sendmail 
# ln -fs /usr/local/exim/bin/exim /usr/sbin/sendmail 
# ln -fs /usr/local/exim/bin/exim /usr/bin/mailq 
# ln -fs /usr/local/exim/bin/exim /usr/bin/runq

После этого требуется удалить из объектных файлов Exim'а таблицы имен и информацию о номерах строк, для этого выполним:

# strip /usr/local/exim/bin/exim*
# exit

Прежде чем приступить к конфигурированию Exim'а, нужно создать базу данных, пользователя и соответствующие таблицы, для этого выполним:

$ /usr/local/mysql/bin/mysql -u myadmin -p
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3760 to server version: 4.1.10a-log

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql>
mysql> CREATE DATABASE exim;
mysql> GRANT ALL PRIVILEGES ON exim.* TO sqlmail@localhost
    -> IDENTIFIED BY 'my_password' WITH GRANT OPTION;
mysql> quit

Когда база данных и пользователь созданы, нужно создать структуру таблиц и ввести данные о пользователях. Можно это делать в консоли MySQL, но мы создадим файл exim.sql, который затем создаст нам все необходимое.

$ ee exim.sql
# Таблица alias
CREATE TABLE aliases (
  local_part varchar(64) NOT NULL default '',
  domain varchar(128) NOT NULL default 'mydomain.ru',
  recipients text,
  PRIMARY KEY  (local_part,domain)
);

# Добавим данные в таблицу aliases
INSERT INTO aliases VALUES ('postmaster', 'mydomain.ru', 'admin');
INSERT INTO aliases VALUES ('mailer-daemon', 'mydomain.ru', 'postmaster');
INSERT INTO aliases VALUES ('root', 'mydomain.ru', 'postmaster');
INSERT INTO aliases VALUES ('bin', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('daemon', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('sync', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('mail', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('pop', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('uucp', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('ftp', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('nobody', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('www', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('named', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('postgres', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('mysql', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('squid', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('operator', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('abuse', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('hostmaster', 'mydomain.ru', 'root');
INSERT INTO aliases VALUES ('webmaster', 'mydomain.ru', 'root');

# Таблица доменов domains
CREATE TABLE domains (
  domain varchar(128) NOT NULL default '',
  type enum('LOCAL','RELAY','VIRTUAL') default 'LOCAL',
  PRIMARY KEY  (domain)
);

# Добавим наш домен в таблицу
INSERT INTO domains VALUES ('mydomain.ru', 'LOCAL');

# Таблица userforward
CREATE TABLE userforward (
  local_part varchar(64) NOT NULL default '',
  domain varchar(128) NOT NULL default '',
  recipients text,
  PRIMARY KEY  (local_part,domain)
);

# Таблица пользователей users, 
CREATE TABLE users (
  login varchar(64) NOT NULL default '',
  name varchar(128) NOT NULL default '',
  password varchar(64) NOT NULL default '',
  decrypt varchar(64) NOT NULL default '',
  uid int(10) unsigned default '1003',
  gid int(10) unsigned default '6',
  domain varchar(128) NOT NULL default 'mydomain.ru',
  quota tinyint(4) default '0',
  status enum('0','1') default '1',
  PRIMARY KEY  (login,domain)
);

Произведем непосредственное создание таблиц, данных и создадим нашего первого пользователя.

$ /usr/local/mysql/bin/mysql -u sqlmail -p exim
mysql> \. exim.sql
mysql> INSERT INTO users (login,name,password,decrypt) 
    -> VALUES ('admin','Администратор',encrypt('my_password'),'my_password');
mysql> quit

Пришло время конфигурирования Exim. Перейдем в каталог, где находится файл конфигурации configure и проделаем следующее:

$ cd /usr/local/exim
$ su
# mv configure configure.default

После этого приступим к редактированию файла конфигурации configure:

# ee configure

######################################################################
#                  Runtime configuration file for Exim               #
######################################################################
#######################################################################
#                    MAIN CONFIGURATION SETTINGS                     #
######################################################################

primary_hostname = mydomain.ru
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   relay_from_hosts = 127.0.0.1
auth_advertise_hosts = *
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_protocol_error \
        -queue_run
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_mime = acl_check_mime
qualify_domain = mydomain.ru
allow_domain_literals = false
never_users = root
host_lookup = *
rfc1413_hosts = *
rfc1413_query_timeout = 0s
ignore_bounce_errors_after = 30m
timeout_frozen_after = 3d
freeze_tell = postmaster
message_size_limit = 10M
smtp_accept_max = 100
smtp_accept_max_per_connection = 5
smtp_accept_max_per_host = 2
split_spool_directory = true
remote_max_parallel = 15
smtp_banner = "Welcome on our mail server!\n\
    This system does not accept Unsolicited \
    Commercial Email\nand will blacklist \
    offenders via our spam processor.\nHave a \
    nice day!\n\n${primary_hostname} ESMTP"

hide mysql_servers = localhost/exim/sqlmail/my_password

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

acl_check_rcpt:

  accept  hosts = :

  deny    domains       = +local_domains
          local_parts   = ^[.] : ^.*[@%!/|]

  deny    domains       = !+local_domains
          local_parts   = ^[./|] : ^.*[@%!] : ^.*/\\.\\./

  accept  local_parts   = postmaster
          domains       = +local_domains

  require verify        = sender

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

  deny    message       = Go Away! You are spammer.
          condition     = ${if match{$sender_host_name} \
                          {bezeqint\\.net|net\\.il|dialup|dsl|pool|peer|dhcp} \
                          {yes}{no}}

  deny    message       = rejected because \
  $sender_host_address is in a black list at $dnslist_domain\n$dnslist_text
          log_message   = found in $dnslist_domain
          dnslists      = relays.ordb.org
  deny    message       = message from \
  $sender_host_address rejected - see http://njabl.org/
          log_message   = found in $dnslist_domain
          dnslists      = dnsbl.njabl.org
  deny    message       = rejected because \
  $sender_host_address for bad WHOIS info, see http://www.rfc-ignorant.org/
          log_message   = found in $dnslist_domain
          dnslists      = ipwhois.rfc-ignorant.org
  deny    message       = rejected because $sender_host_address \
  is in a black list at $dnslist_domain\n$dnslist_text
          log_message   = found in $dnslist_domain
          dnslists      = dialups.mail-abuse.org
  deny    message       = rejected because $sender_host_address \
  is in a black list at $dnslist_domain\n$dnslist_text
          log_message   = found in $dnslist_domain
          dnslists      = list.dsbl.org
  deny    message       = Spam blocked see: \
  http://www.spamcop.net/w3m?action=checkblock&ip=$sender_host_address
          log_message   = found in $dnslist_domain
          dnslists      = bl.spamcop.net
  deny    message       = rejected, $sender_host_address \
  Open Proxy, see: $dnslist_domain\n$dnslist_text
          log_message   = found in $dnslist_domain
          dnslists      = dnsbl.void.ru

  accept  domains       = +local_domains
          endpass
          message       = unknown user
          verify        = recipient

  accept  domains       = +relay_to_domains
          endpass
          message       = unrouteable address
          verify        = recipient

  accept  hosts         = +relay_from_hosts
  accept  authenticated = *
  deny    message       = relay not permitted

acl_check_mime:

  warn decode = default

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

  deny message = Sorry, noone speaks chinese here
       condition = ${if eq{$mime_charset}{gb2312}{1}{0}}

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

userforward:
  driver = redirect
  allow_fail
  allow_defer
  data = ${lookup mysql{SELECT recipients FROM userforward \
         WHERE local_part='${local_part}' AND domain='${domain}'}}

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

######################################################################
#                      TRANSPORTS CONFIGURATION                      #
######################################################################
#                       ORDER DOES NOT MATTER                        #
#     Only one appropriate transport is called for each delivery.    #
######################################################################

begin transports

remote_smtp:
  driver = smtp

local_delivery:
  driver = appendfile
  check_string = ""
  create_directory
  delivery_date_add
  directory = /var/mail/$domain/$local_part
  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 = 75%
  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

######################################################################
#                      RETRY CONFIGURATION                           #
######################################################################

begin retry

*                      quota
*                      *           F,2h,15m; G,16h,1h,1.5; F,4d,6h


######################################################################
#                      REWRITE CONFIGURATION                         #
######################################################################

begin rewrite

######################################################################
#                   AUTHENTICATION CONFIGURATION                     #
######################################################################

begin authenticators

auth_plain:
  driver = plaintext
  public_name = PLAIN
  server_condition = ${lookup mysql{SELECT login FROM users \
                        WHERE login = '${quote_mysql:${local_part:$2}}' \
                        AND domain = '${quote_mysql:${domain:$2}}' \
                        AND decrypt = '${quote_mysql:$3}' \
                        AND status = '1'}{yes}{no}}
  server_prompts = :
server_set_id = $2

auth_login:
  driver = plaintext
  public_name = LOGIN
  server_condition = ${lookup mysql{SELECT login FROM users \
                        WHERE login = '${quote_mysql:${local_part:$1}}' \
                        AND domain = '${quote_mysql:${domain:$1}}' \
                        AND decrypt = '${quote_mysql:$2}' \
                        AND status = '1'}{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 decrypt FROM users \
                        WHERE login = '${quote_mysql:${local_part:$1}}' \
                        AND domain = '${quote_mysql:${domain:$1}}' \
                        AND status = '1'}{$value}fail}
server_set_id = $1

# End of Exim configuration file

Создадим каталог для лог-файлов и разберемся с правами на него, на каталог, куда у нас будет складываться почта, и на spool-директорию.

# mkdir /var/log/exim
# chown -R exim:mail /var/log/exim /var/mail /var/spool/mqueue

После этого создадим сценарий, который будет запускать наш почтовый сервер при старте системы.

# ee /usr/local/etc/rc.d/exim.sh
#!/bin/sh
### file exim.sh ###
case "$1" in
  start)
    echo "Starting Exim..."
    /usr/local/exim/bin/exim -bd -q15m
    ;;
  stop)
    echo "Stopping Exim..."
    kill -TERM `cat /var/spool/mqueue/exim-daemon.pid`
    ;;
  restart)
      $0 stop
	  sleep 2
      $0 start
      ;;
  reload)
    echo "Exim reloading..."
    kill -HUP `cat /var/spool/mqueue/exim-daemon.pid`
    ;;
  *)
    echo "Usage: $0 {start|stop|restart|reload}"
    exit 1
    ;;
esac

# chmod +x /usr/local/etc/rc.d/exim.sh

После этого требуется проверить правильность синтаксиса файла конфигурации /usr/local/exim/configure. Выполним:

# /usr/local/exim/bin/exim -bV
Exim version 4.50 #1 built 02-Apr-2005 19:12:40
Copyright (c) University of Cambridge 2004
Probably Berkeley DB version 1.8x (native mode)
Support for: Perl OpenSSL Content_Scanning Old_Demime
Lookups: lsearch wildlsearch nwildlsearch iplsearch dbm dbmnz mysql
Authenticators: cram_md5 plaintext
Routers: accept dnslookup ipliteral manualroute queryprogram redirect
Transports: appendfile/maildir autoreply pipe smtp
Fixed never_users: 0
Configuration file is /usr/local/exim/configure

Если ошибок не обнаружено, идем дальше... Теперь требуется проверить, будет ли осуществляться распознавание сообщений для локальных пользователей, для этого выполним:

# /usr/local/exim/bin/exim -bt postmaster
admin@mydomain.ru
    <-- postmaster@mydomain.ru
  router = virtual_localuser, transport = local_delivery

Если ошибок не обнаружено, идем дальше... Теперь требуется проверить, будет ли осуществляться распознавание сообщений для внешних пользователей, для этого выполним:

# /usr/local/exim/bin/exim -bt someuser@msn.com
someuser@msn.com
  router = dnslookup, transport = remote_smtp
  host mx1.hotmail.com [65.54.166.99]  MX=5
  host mx1.hotmail.com [65.54.252.99]  MX=5
  host mx1.hotmail.com [64.4.50.99]    MX=5
  host mx1.hotmail.com [64.4.50.50]    MX=5
  host mx4.hotmail.com [65.54.190.230] MX=5
  host mx4.hotmail.com [65.54.190.179] MX=5
  host mx4.hotmail.com [65.54.167.230] MX=5
  host mx4.hotmail.com [65.54.253.230] MX=5
  host mx3.hotmail.com [65.54.253.99]  MX=5
  host mx3.hotmail.com [65.54.167.5]   MX=5
  host mx3.hotmail.com [64.4.50.239]   MX=5
  host mx3.hotmail.com [64.4.50.179]   MX=5
  host mx2.hotmail.com [65.54.190.50]  MX=5
  host mx2.hotmail.com [65.54.190.7]   MX=5
  host mx2.hotmail.com [65.54.252.230] MX=5
  host mx2.hotmail.com [65.54.166.230] MX=5

Если ошибок не обнаружено, идем дальше... Теперь требуется проверить, будет ли осуществляться доставка сообщений локальным пользователям, для этого выполним:

# /usr/local/exim/bin/exim -v postmaster@mydomain.ru
From: admin@mydomain.ru
To: postmaster@mydomain.ru
Subject: Testing Exim

This is a test message.
^D
LOG: MAIN
  <= root@mydomain.ru U=root P=local S=325
# delivering 1DOs2P-000HAY-0E
LOG: MAIN
  => admin  R=virtual_localuser T=local_delivery
LOG: MAIN
  Completed
^C

Если ошибок не обнаружено, идем дальше... Теперь требуется проверить, будет ли осуществляться доставка сообщений внешним пользователям, для этого выполним:

# exim -v someuser@msn.com 
From: admin@mydomain.ru
To: someuser@msn.com
Subject: Testing Exim

This is a test message.
^D
LOG: MAIN
  <= root@mydomain.ru U=root P=local S=303
# LOG: MAIN
  => someuser  R=dnslookup T=remote_smtp
LOG: MAIN
  Completed
^C

На данном этапе мы убедились, что Exim правильно настроен и работает, теперь осталось проверить SMTP аутентификацию, но прежде внесем правку. SMTP аутентификация нам нужна при отправке сообщний с виртуальных хостов (если у тебя таковые имеются) или когда пользователь, находящийся вне офиса, отправляет почту через свой аккаунт. Для внутренней сети аутентификацию я не применяю. Поэтому изменим в файле /usr/local/exim/configure строку

hostlist   relay_from_hosts = 127.0.0.1

на

hostlist   relay_from_hosts = 127.0.0.1 : 192.168.10.0/24

Как редактировать файлы, я думаю, ты не забыл?

Запускаем Exim, выполнив:

# /usr/local/etc/rc.d/exim.sh start

Для проверки аутентификации нам понадобится конвертер, установим его из портов:

# cd /usr/ports/converters/mmencode
# make install
# exit

Формируем строку PLAIN аутентификации (Netscape), для этого выполним:

$ printf 'admin@mydomain.ru\0admin@mydomain.ru\0my_password' | mmencode
YWRtaW5AbXlkb21haW4ucnUAYWRtaW5AbXlkb21haW4ucnUAbXlfcGFzc3dvcmQ=
$ telnet localhost 25
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.ru.
Escape character is '^]'.
220-Welcome on our mail server!
220-This system does not accept Unsolicited Commercial Email
220-and will blacklist offenders via our spam processor.
220-Have a nice day!
220-
220 mydomain.ru ESMTP
ehlo localhost
250-mydomain.ru Hello localhost.ru [127.0.0.1]
250-SIZE 10485760
250-PIPELINING
250-AUTH PLAIN LOGIN CRAM-MD5
250 HELP
auth plain
334 
YWRtaW5AbXlkb21haW4ucnUAYWRtaW5AbXlkb21haW4ucnUAbXlfcGFzc3dvcmQ=
235 Authentication succeeded 
quit
221 mydomain.ru closing connection
Connection closed by foreign host.

Из примера видно, что все прошло успешно, если у вас иной результат, то тогда еще раз внимательно читаем и ищем ошибку...

Итак, идем дальше, формируем строку LOGIN аутентификации (Outlook), для этого выполним:

$ echo -n admin@mydomain.ru | mimencode -b
YWRtaW5AbXlkb21haW4ucnU=
$ echo -n my_passowrd | mimencode -b
bXlfcGFzc293cmQ=
$ telnet localhost 25
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.ru.
Escape character is '^]'.
220-Welcome on our mail server!
220-This system does not accept Unsolicited Commercial Email
220-and will blacklist offenders via our spam processor.
220-Have a nice day!
220-
220 mydomain.ru ESMTP
ehlo localhost
250-mydomain.ru Hello localhost.ru [127.0.0.1]
250-SIZE 10485760
250-PIPELINING
250-AUTH PLAIN LOGIN CRAM-MD5
250 HELP
auth login
334 VXNlcm5hbWU6
YWRtaW5AbXlkb21haW4ucnU=
334 UGFzc3dvcmQ6
bXlfcGFzc293cmQ=
235 Authentication succeeded 
quit
221 mydomain.ru closing connection
Connection closed by foreign host.

Из примера видно, что все прошло успешно, если у вас иной результат, то тогда еще раз внимательно читаем и ищем ошибку...

Идем дальше, проверяем CRAM-MD5 аутентификацию (The Bat!).

Далее я приведу полный текст, изложенный в статье Ginger, т.к. описанный процесс длиный и трудоемкий. Но при желании ты можешь его пройти. Я же поступил простым способом. Запустил свой The Bat!, в свойствах ящика в разделе транспорт в настройках отправки почты поставил галочку Аутентификация SMTP (RFC-2554) и отправил письмо. Проверить отправку ты сможешь, посмотрев сначала в журнале The Bat!, а затем в логах Exim.

Итак, проверяем CRAM-MD5 аутентификацию.


su-2.05a# telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.localdomain.
Escape character is '^]'.
220 smtp.example.ru ESMTP Exim 4.20 Fri, 05 Sep 2003 14:09:15 +0400
ehlo localhost
250-smtp.example.ru Hello localhost.localdomain [127.0.0.1]
250-SIZE 10485760
250-PIPELINING
250-AUTH PLAIN LOGIN CRAM-MD5
250 HELP
auth cram-md5
334 PDI2NDAyLjEwNTY2MTE1NTVAc210cC5leGFtcGxlLnJ1Pg==
Z2luZ2VyQGV4YW1wbGUucnUgYjllN2Q1OTUxMGMxNjE0YzIwYjU4N2JkNmFkODk2MDI=
235 Authentication succeeded
quit
221 smtp.example.ru closing connection
Connection closed by foreign host.

Из примера видно, что все прошло успешно, если у вас иной результат, то тогда еще раз внимательно читаем и ищем ошибку...

Итак, опишу свои действия... После того как была выполнена команда auth cram-md5, появилась строка вида:

334 PDI2NDAyLjEwNTY2MTE1NTVAc210cC5leGFtcGxlLnJ1Pg==

После этого мы должны выполнить следующее:

su-2.05a# perl -MMIME::Base64 -e 'print MIME::Base64::decode_base64(shift), "\n"' \
PDI2NDAyLjEwNTY2MTE1NTVAc210cC5leGFtcGxlLnJ1Pg==

<26402.1056611555@smtp.example.ru>

Затем выполним:

su-2.05a# perl -MMIME::Base64 -MDigest::HMAC_MD5 -e \
'print MIME::Base64::encode_base64($ARGV[0] . " " . \
Digest::HMAC_MD5::hmac_md5_hex($ARGV[2], $ARGV[1]))' \
'ginger@example.ru' 'my-secret-pass' '<26402.1056611555@smtp.example.ru>'

Z2luZ2VyQGV4YW1wbGUucnUgYjllN2Q1OTUxMGMxNjE0YzIwYjU4N2JkNmFkODk2MDI=

Полученный результат и есть интересующая нас строка.


Проверим защищенное соединение TLS/SSL

$ openssl
OpenSSL> s_client -host localhost -port 465
CONNECTED(00000003)
depth=0 /C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster
@mydomain.ru
verify error:num=18:self signed certificate
verify return:1
depth=0 /C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster
@mydomain.ru
verify return:1
---
Certificate chain
 0 s:/C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster@my
domain.ru
   i:/C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster@my
domain.ru
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIEADCCA2mgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCUlUx
MDYxNTQ5MzhaMIG3MQswCQYDVQQGEwJSVTEcMBoGA1UECBMTU3RhdnJvcG9sIFRl
bDE3MDUGA1UEChQuRWRpdG9yaWFsIG9mIE5ld3NwYXBlciAiU3RhdnJvcG9sc2th
eWEgUHJhdmRhIjEVMBMGA1UEAxMMc3RhcHJhdmRhLnJ1MSYwJAYJKoZIhvcNAQkB
Fhdwb3N0bWFzdGVyQHN0YXByYXZkYS5ydTAeFw0wNTA0MjIxNTQ5MzhaFw0zMjA5
MDYxNTQ5MzhaMIG3MQswCQYDVQQGEwJSVTEcMBoGA1UECBMTU3RhdnJvcG9sIFRl
cnJpdG9yeTESMBAGA1UEBxMJU3RhdnJvcG9sMTcwNQYDVQQKFC5FZGl0b3JpYWwg
b2YgTmV3c3BhcGVyICJTdGF2cm9wb2xza2F5YSBQcmF2ZGEiMRUwEwYDVQQDEwxz
MDYxNTQ5MzhaMIG3MQswCQYDVQQGEwJSVTEcMBoGA1UECBMTU3RhdnJvcG9sIFRl
LnJ1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNFT87/U4Y/isxCQR2aD0n
LgJ9DvOwWBFs72R1ciYvOUOQVWGAhMCeGjVkiPtysjFtRRdgKjGvKrKmx9C3pH3w
ZN5fXFcZBqUtiLzQ0VKcdw3utAvRM4gzICSlktpjPtxfoYYSC4CUHA1/0+WDC7HO
HVyM7oBL+pRZyQT0A/8k7QIDAQABo4IBGDCCARQwHQYDVR0OBBYEFIVHLfnzpOO5
ub9oTTvksXmf9F7gMIHkBgNVHSMEgdwwgdmAFIVHLfnzpOO5ub9oTTvksXmf9F7g
oYG9pIG6MIG3MQswCQYDVQQGEwJSVTEcMBoGA1UECBMTU3RhdnJvcG9sIFRlcnJp
dG9yeTESMBAGA1UEBxMJU3RhdnJvcG9sMTcwNQYDVQQKFC5FZGl0b3JpYWwgb2Yg
HVyM7oBL+pRZyQT0A/8k7QIDAQABo4IBGDCCARQwHQYDVR0OBBYEFIVHLfnzpOO5
MDYxNTQ5MzhaMIG3MQswCQYDVQQGEwJSVTEcMBoGA1UECBMTU3RhdnJvcG9sIFRl
ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAA4EghSwza0gbNcik
HVyM7oBL+pRZyQT0A/8k7QIDAQABo4IBGDCCARQwHQYDVR0OBBYEFIVHLfnzpOO5
eWEyOMmOGQbx/++XLU35CFrdiqEwdZTVfZscoefyhp9shsG1Dw8zbVK+im7wmmA1
zokwLdwUQZGVBRTefUivYOSOJ9c=
-----END CERTIFICATE-----
subject=/C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster
@mydomain.ru
issuer=/C=RU/ST=RU/L=Stavropol/O=Example/CN=mydomain.ru/emailAddress=postmaster@
mydomain.ru
---
No client certificate CA names sent
---
SSL handshake has read 1190 bytes and written 340 bytes
---
New, TLSv1/SSLv3, Cipher is AES256-SHA
Server public key is 1024 bit
SSL-Session:
    Protocol  : TLSv1
    Cipher    : AES256-SHA
    Session-ID: FF415922B742484BA13B84FAD575C528B3549B742484BA13AB1393C4AB3D8010

    Session-ID-ctx:
    Master-Key: 8A39B36BFDB180ED55F829DBC420D675F0ECAF1B07FBBED3A1632A3AA8ECCE96
4CAACF8CDAE5F829DBC420D3F5AE9BA8
    Key-Arg   : None
    Start Time: 1114190806
    Timeout   : 300 (sec)
    Verify return code: 18 (self signed certificate)
---
220-Welcome on our mail server!
220-This system does not accept Unsolicited Commercial Email
220-and will blacklist offenders via our spam processor.
220-Have a nice day!
220-
220 mydomain.ru ESMTP

Дальнейшую проверку можно произвести по вышеуказанной аналогии, а смысл? И так видно, что все Ок!

quit
OpenSSL> quit

Последним шагом мы настроим ротацию логов, чтобы они у нас не разрослись до бесконечности.

$ su
# crontab -e -u exim

Здесь тебя может ждать неприятность, если ты еще не познакомился с редактором vi. Я тебе немного помогу, но в дальнейшем - учи матчасть.

Нажми клавишу i и введи следующую строку:

@daily /usr/local/exim/bin/exicyclog

После ввода строки нажми не ENTER, а ESC. Теперь последовательно нажми :wq. Тем самым ты сохранишь изменения и выйдешь из редактора. Теперь наши логи будут обновляться каждые сутки в полночь.

Вот и все. На этом установку и настройку Exim можно считать полностью завершенной.

Теперь наш сервер может принимать и отправлять почту. Но как эту почту доставить конечному пользователю? Об этом пойдет рассказ далее.


Устанавливаем SpamAssassin

Использование только одного метода при борьбе со спамом ни к чему не приведет. Только комплекс мер может принести результат.

Первый рубеж мы воздвигли в конфигурационном файле Exim прописав так называемые dnsbl, то есть черные списки MTA с открытым релеем, и заблокировав прием писем с хостов из этих списков.

Следующим рубежом у нас станет почтовый фильтр SpamAssasin.

Устанавливается он не просто, а очень просто.

$ su
# perl -MCPAN -e shell
o conf prerequisites_policy ask

При первом запуске cpan shell потребуется ответить на несколько элементарных вопросов. А дальше еще проще.

cpan> install Digest::SHA1
cpan> install HTML::Parser
cpan> install Net::DNS
cpan> install Mail::SpamAssassin
cpan> quit

Переместим каталог spamassassin из корневого раздела нашей системы в радел /usr. А также отредактируем конфигурационный файл.

# mv /etc/mail/spamassassin/ /usr/local/etc/
# ee /usr/local/etc/spamassasing/local.cf
trusted_networks 192.168/16 127/8

required_score          5.0

report_safe             1
rewrite_header subject ***SPAM***

use_bayes               1
bayes_path              /usr/local/etc/spamassassin/bayes/
bayes_file_mode         0666
bayes_min_ham_num       1
bayes_min_spam_num      1
bayes_auto_learn        1
auto_learn_threshold_nonspam    -2
auto_learn_threshold_spam        8
bayes_learn_to_journal  1
skip_rbl_checks         0

ok_languages            ru en
ok_locales              ru en

Создадим скрипт для запуска нашего фильтра и присвоим ему исполняемый бит.

# ee /usr/local/etc/rc.d/01spamd.sh
#!/bin/sh
#
# Startup / shutdown script for SpamAssassin daemon

case "$1" in
  start)
    /usr/local/bin/spamd -d -u exim \
    --siteconfigpath=/usr/local/etc/spamassassin && echo -n ' spamd'
  ;;
  stop)
    spamdpid=`ps -ax | grep spamd | grep -v grep | grep -v sh | \
    awk '{ print $1 }'`
      if [ "$spamdpid" != "" ]; then
        kill $spamdpid > /dev/null 2>&1
        echo -n " spamd"
      fi
  ;;
  *)
    echo "Usage: `basename $0` {start|stop}" >&2
  ;;
esac
exit 0

chmod +x /usr/local/etc/rc.d/01spamd.sh

Создадим Bayes-базу и разберемся с правами на нее. Если ты обратил внимание, то в стартовом скрипте при запуске мы испольуем опцию -u exim, которая запускает демон spamassassin от пользователя exim.

# /usr/local/bin/sa-learn --sync
# chowm -R exim /usr/local/etc/spamassassin/bayes
# chmod 666 /usr/local/etc/spamassassin/bayes/*

Пришло вермя запустить spamassassin.

# /usr/local/etc/rc.d/01spamd.sh start

Теперь свяжем Exim с SpamAssassin. Для этого дополним файл /usr/local/exim/configure необходимыми опциями.

Окончательный вид файла конфигураци /usr/local/exim/configure будет следующий:

(внесенные изменения выделены полужирным шрифтом)


######################################################################
#                  Runtime configuration file for Exim               #
######################################################################
#######################################################################
#                    MAIN CONFIGURATION SETTINGS                     #
######################################################################

primary_hostname = mydomain.ru
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   relay_from_hosts = 127.0.0.1
auth_advertise_hosts = *
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_protocol_error \
        -queue_run
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_mime = acl_check_mime
acl_smtp_data = acl_check_content
av_scanner = clamd:/usr/local/clamav/var/tmp/clamd.sock
spamd_address = 127.0.0.1 783
qualify_domain = mydomain.ru
allow_domain_literals = false
never_users = root
host_lookup = *
rfc1413_hosts = *
rfc1413_query_timeout = 0s
ignore_bounce_errors_after = 30m
timeout_frozen_after = 3d
freeze_tell = postmaster
message_size_limit = 10M
smtp_accept_max = 100
smtp_accept_max_per_connection = 5
#smtp_connect_backlog = 50
smtp_accept_max_per_host = 2
split_spool_directory = true
remote_max_parallel = 15
smtp_banner = "Welcome on our mail server!\n\
    This system does not accept Unsolicited \
    Commercial Email\nand will blacklist \
    offenders via our spam processor.\nHave a \
    nice day!\n\n${primary_hostname} ESMTP"

hide mysql_servers = localhost/exim/sqlmail/my_password

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

acl_check_rcpt:

  accept  hosts = :

  deny    domains       = +local_domains
          local_parts   = ^[.] : ^.*[@%!/|]

  deny    domains       = !+local_domains
          local_parts   = ^[./|] : ^.*[@%!] : ^.*/\\.\\./

  accept  local_parts   = postmaster
          domains       = +local_domains

  require verify        = sender

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

  deny    message       = Go Away! You are spammer.
          condition     = ${if match{$sender_host_name} \
                          {bezeqint\\.net|net\\.il|dialup|dsl|pool|peer|dhcp} \
                          {yes}{no}}

  deny    message       = rejected because \
  $sender_host_address is in a black list at $dnslist_domain\n$dnslist_text
          log_message   = found in $dnslist_domain
          dnslists      = relays.ordb.org
  deny    message       = message from \
  $sender_host_address rejected - see http://njabl.org/
          log_message   = found in $dnslist_domain
          dnslists      = dnsbl.njabl.org
  deny    message       = rejected because \
  $sender_host_address for bad WHOIS info, see http://www.rfc-ignorant.org/
          log_message   = found in $dnslist_domain
          dnslists      = ipwhois.rfc-ignorant.org
  deny    message       = rejected because $sender_host_address \
  is in a black list at $dnslist_domain\n$dnslist_text
          log_message   = found in $dnslist_domain
          dnslists      = dialups.mail-abuse.org
  deny    message       = rejected because $sender_host_address \
  is in a black list at $dnslist_domain\n$dnslist_text
          log_message   = found in $dnslist_domain
          dnslists      = list.dsbl.org
  deny    message       = Spam blocked see: \
  http://www.spamcop.net/w3m?action=checkblock&ip=$sender_host_address
          log_message   = found in $dnslist_domain
          dnslists      = bl.spamcop.net
  deny    message       = rejected, $sender_host_address \
  Open Proxy, see: $dnslist_domain\n$dnslist_text
          log_message   = found in $dnslist_domain
          dnslists      = dnsbl.void.ru

  accept  domains       = +local_domains
          endpass
          message       = unknown user
          verify        = recipient

  accept  domains       = +relay_to_domains
          endpass
          message       = unrouteable address
          verify        = recipient

  accept  hosts         = +relay_from_hosts
  accept  authenticated = *
  deny    message       = relay not permitted

acl_check_mime:

  warn decode = default

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

  deny message = Sorry, noone speaks chinese here
       condition = ${if eq{$mime_charset}{gb2312}{1}{0}}

accept

acl_check_content:

  deny  message = Virus found ($malware_name)
        malware = *

  warn  message = X-Spam-Score: $spam_score ($spam_bar)
        hosts   = !+relay_from_hosts
        spam    = nobody:true

  warn  message = X-Spam-Report: $spam_report
        hosts   = !+relay_from_hosts
        spam    = nobody:true

  warn  message = Subject: ***SPAM*** $h_Subject:
        hosts   = !+relay_from_hosts
        spam    = nobody

  deny  message = This message scored $spam_score spam points.
        spam    = nobody:true
        hosts   = !+relay_from_hosts
        condition = ${if >{$spam_score_int}{120}{1}{0}}

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

userforward:
  driver = redirect
  allow_fail
  allow_defer
  data = ${lookup mysql{SELECT recipients FROM userforward \
         WHERE local_part='${local_part}' AND domain='${domain}'}}

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

######################################################################
#                      TRANSPORTS CONFIGURATION                      #
######################################################################
#                       ORDER DOES NOT MATTER                        #
#     Only one appropriate transport is called for each delivery.    #
######################################################################

begin transports

remote_smtp:
  driver = smtp

local_delivery:
  driver = appendfile
  check_string = ""
  create_directory
  delivery_date_add
  directory = /var/mail/$domain/$local_part
  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 = 75%
  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

######################################################################
#                      RETRY CONFIGURATION                           #
######################################################################

begin retry

*                      quota
*                      *           F,2h,15m; G,16h,1h,1.5; F,4d,6h


######################################################################
#                      REWRITE CONFIGURATION                         #
######################################################################

begin rewrite

######################################################################
#                   AUTHENTICATION CONFIGURATION                     #
######################################################################

begin authenticators

auth_plain:
  driver = plaintext
  public_name = PLAIN
  server_condition = ${lookup mysql{SELECT login FROM users \
                        WHERE login = '${quote_mysql:${local_part:$2}}' \
                        AND domain = '${quote_mysql:${domain:$2}}' \
                        AND decrypt = '${quote_mysql:$3}' \
                        AND status = '1'}{yes}{no}}
  server_prompts = :
server_set_id = $2

auth_login:
  driver = plaintext
  public_name = LOGIN
  server_condition = ${lookup mysql{SELECT login FROM users \
                        WHERE login = '${quote_mysql:${local_part:$1}}' \
                        AND domain = '${quote_mysql:${domain:$1}}' \
                        AND decrypt = '${quote_mysql:$2}' \
                        AND status = '1'}{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 decrypt FROM users \
                        WHERE login = '${quote_mysql:${local_part:$1}}' \
                        AND domain = '${quote_mysql:${domain:$1}}' \
                        AND status = '1'}{$value}fail}
server_set_id = $1

# End of Exim configuration file

После внесения изменений нам остается только перезапустить Exim.

# /usr/local/etc/rc.d/exim.sh restart

Наша связка начинает сразу работать с заранее предустановленными параметрами. Но что бы увеличить вероятность правильного срабатывания для отсеевания спама необходимо систему обучить - скормить ей более 200 писем с примерами "белых писем" (ham) и более 200 спамовых писем (spam).

Обучение может производится автоматически, с помощью bayes_auto_learn - когда очки письма (без учета очков за AWL, BAYES_XX, BLACKLIST и WHITELIST) выходят за пределы между auto_learn_threshold_nonspam и auto_learn_threshold_spam. Это можно узнать если в заголовке письма в поле X-Spam-Status: присутствует запись autolearn=spam или autolearn=ham.

И обечение может производиться вручную (или при помощи скрипта) командой sa-lean. Для этого нужно скормить каталог писем со спамом spam и каталог благонадежных писем ham. Письма нужно иметь в немодивицированном виде (без forward`а и прочих почтовых пересылок перенаправлений). Я получал их прямым экспортом из программы TheBat в формат .MSG. Для удобства завёл специальные папочки в которые складывал примеры писем. После накопления необходимого количесва писем, экспортировал их на сервер и скормил обучалке:

$ su
# /usr/local/bin/sa-learn --ham /home/admin/ham/
# /usr/local/bin/sa-learn --spam /home/admin/spam/

"А как же ты экспортировал письма на сервер?" - спросишь ты. А для этого у мы установим FTP-сервер. И об этом пойдет речь далее.