Реализация Exim + OpenDKIM для массовых рассылок

    Все было готово к запуску нового проекта, осталась одна проблема – письма подтверждения регистрации Gmail отправлял в спам. Ознакомившись со справкой сервиса Gmail, я понял, что основной проблемой является отсутствие реализации DKIM на сервере. В моем случае, отправкой писем занимался Exim. Необходимо было реализовать связку Exim+DKIM. Увы, из-за смены утилиты реализации DKIM на FreeBSD все инструкции устарели. Мне пришлось вслепую настраивать Exim+DKIM несколько дней. В этой статье я опишу все этапы реализации Exim+DKIM.

    DKIM необходим для массовых рассылок и отправки автоматических писем с сайта. Без наличия этой технологии спам фильтры Gmail не пропускают письма, отправленные скриптами. Для того чтобы Exim оправлял письма, подписанные ключем DKIM, необходимо поставить дополнительную утилиту OpenDKIM. Ранее использовалась утилита dkim-filter, разработчики прекратили ее поддержку.

    Советую ознакомиться со статьей. В ней доходчиво написано, как добиться, чтобы ваши письма не попадали в спам. Статья устарела для реализации связки Exim+DKIM, все остальное – актуально.

    Установка реализовывалась в 5 этапов.



    1. Установка OpenDKIM

    cd /usr/ports/mail/opendkim
    make install clean

    2. Генерируем ключи и настраиваем OpenDKIM и DNS записи

    Для начала нужно сгенерировать пару ключей — внешний и внутренний. Внешний будет храниться в DNS записях, а внутренний – на сервере.

    Итак, генерируем ключи:

    opendkim-genkey -D /var/db/opendkim -d <domain.name> -s

    /var/db/opendkim — директория, где будут лежать ключи
    <domain.name> - доменное имя (example.com)
    selector — указываем селектор, (например, mail). Селектор будет использоваться в DNS записи и указываться в настройке транспортов exim`а.

    В нашем случае пишем следующее:

    opendkim-genkey -D /var/db/opendkim -d example.com -s mail


    Итак, создается два файла — mail.txt (mail — выбранный нами селектор) и mail.private. Посмотрим их содержимое:

    cat /var/db/opendkim/mail.txt
    mail._domainkey IN TXT ( "v=DKIM1; k=rsa; t=s; " "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GN … mpwIDAQAB" ) ; ----- DKIM key mail for example.com.

    Содержимое файла mail.txt добавим в DNS зону example.com, причем вот в таком виде (скобки и кавычки нужно удалить):

    mail._domainkey IN TXT "v=DKIM1; k=rsa; t=s; p=MIGfMA0GCSqGS … wIDAQAB"

    Советуют еще добавить вот такую DNS запись:

    _adsp._domainkey.example.com IN TXT "dkim=unknown"

    Дальше на файл mail.private устанавливаем права, чтобы его мог прочитать exim и только он. В нашем случае вот так:

    chown mailnull:mail /var/db/opendkim/mail.private
    chmod 600 /var/db/opendkim/mail.private

    Добавим в config файле opendkim путь к mail.private. Покажу пример моего config:

    vim (or something else) /usr/local/etc/opendkim.conf

    # This is a simple config file for signing and verifying

    LogWhy yes
    Syslog yes
    SyslogSuccess yes

    Canonicalization relaxed/simple

    Domain example.com
    Selector mail
    KeyFile /var/db/opendkim/mail.private

    Socket inet:8891@localhost

    ReportAddress support@example.com
    SendReports yes

    ## Hosts to sign email for - 127.0.0.1 is default
    ## See the OPERATION section of opendkim(8) for more information
    #
    # InternalHosts 192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12

    ## For secondary mailservers - indicates not to sign or verify messages
    ## from these hosts
    #
    # PeerList X.X.X.X

    # PidFile /var/run/opendkim/opendkim.pid


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

    C opendkim`ом закончили. Перейдем к настройке Exim.

    3. Настройка Exim

    Поправим секцию transports:

    vim /usr/local/etc/exim/configure

    Находим begin transports и сразу же ниже добавляем:

    DKIM_DOMAIN = ${lc:${domain:$h_from:}}
    DKIM_FILE = /var/db/opendkim/mail.private #- важно!!!! Пишем путь к приватному ключу
    DKIM_PRIVATE_KEY = ${if exists{DKIM_FILE}{DKIM_FILE}{0}}

    Также заменяем:

    remote_smtp:
    driver = smtp

    На:

    remote_smtp:
    driver = smtp
    dkim_domain = DKIM_DOMAIN
    dkim_selector = mail # - выбранный нами селектор.
    dkim_private_key = DKIM_PRIVATE_KEY

    4. Дальше нужно запустить milter_opendkim

    /usr/local/etc/rc.d/milter-opendkim start

    5. Перезапустить Exim

    /usr/local/etc/rc.d/exim reload

    Exim запустился, делаем пробную отправку письма.

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

    DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d= example.com; s=mail;
    h=Date:Message-Id:From:Content-type:MIME-Version:Subject:To; bh=RUN…0qKg=;
    b=EEla1LMRm…Q6CYJM/VHg=;

    Received: from example by xxx.freehost.com.ua with local (Exim 4.80.1 (FreeBSD))

    Если запись не появилась – DKIM не запустился.

    Напоследок пару слов о сервисе, для которого я это делал. С середины лета все свободное время работал над проектом "умного" подбора фильмов. Сайт перешел с альфа тестирования в бета. Буду благодарен за полезные советы и замечания по работе сервиса. Вот ссылка.
    Метки:
    • +5
    • 20,5k
    • 6
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 6
    • 0
      1. Как это реализовать на CentOS 5?
      2. Через что отправляете письма? PHP — mail()?
      • 0
        1. Размещение конфигов будет в других директориях и еще opendkim нужно поставить (yum например).
        2. Вся почта идет через Exim, не только mail().
      • 0
        А зачем opendkim нужно запускать? Я ключи еще dkim-filter генерировал, а сейчас использую только exim4 без каких-либо dkim пакетов, и работает.
        • +3
          Если доменов много, то делаю так (у меня на Ubuntu 10.04 стоит dk-milter и OpenDKIM):
          /etc/default/opendkim
          # Command-line options specified here will override the contents of
          # /etc/opendkim.conf. See opendkim(8) for a complete list of options.
          #DAEMON_OPTS=""
          #
          # Uncomment to specify an alternate socket
          # Note that setting this will override any Socket value in opendkim.conf
          SOCKET="inet:10035@localhost" # listen on loopback on port 8891 - Ubuntu default
          


          /etc/opendkim.conf
          # This is a basic configuration that can easily be adapted to suit a standard
          # installation. For more advanced options, see opendkim.conf(5) and/or
          # /usr/share/doc/opendkim/examples/opendkim.conf.sample.
          
          ##
          ## opendkim.conf -- configuration file for OpenDKIM filter
          ##
          ADSPAction              Continue
          ADSPNoSuchDomain        Yes
          AutoRestart             Yes
          AutoRestartRate         10/1h
          Canonicalization        simple/simple
          ExternalIgnoreList      refile:/etc/mail/opendkim/trusted-hosts
          InternalHosts           refile:/etc/mail/opendkim/trusted-hosts
          KeyTable                refile:/etc/mail/opendkim/keyTable
          LogWhy                  Yes
          On-Default              accept
          On-BadSignature         accept
          On-DNSError             tempfail
          On-InternalError        accept
          On-NoSignature          accept
          #On-Security             tempfail
          On-Security             accept
          PidFile                 /var/run/opendkim/dkim-milter.pid
          SignatureAlgorithm      rsa-sha1
          SigningTable            refile:/etc/mail/opendkim/signingTable
          Socket                  inet:10035@127.0.0.1
          Syslog                  Yes
          SyslogSuccess           Yes
          TemporaryDirectory      /tmp
          UMask                    000
          UserID                  opendkim:postfix
          #X-Header                Yes
          


          /etc/default/dk-filter
          USER="postfix"
          GROUP="mail"
          SOCKET="inet:10034@127.0.0.1"
          
          SIGNING_DOMAIN="/etc/mail/domainkeys/domains"
          KEYFILE="/etc/mail/domainkeys/keys"
          SELECTOR_NAME="mail"
          SIGNER=yes
          VERIFIER=yes
          CANON=simple
          #REJECTION="bad=r,dns=t,int=t,no=a,miss=r"
          REJECTION="bad=r,dns=t,int=a,no=a,miss=r"
          HOSTLIST="/etc/mail/domainkeys/trusted-hosts"
          EXTRA_ARGS="-A -H -k -l -D -i ${HOSTLIST} -I ${HOSTLIST}"
          #========================================================
          MODES=
          if [ x${SIGNER:0:1} == "xy" -o x${SIGNER:0:1} == "xY" ]; then
              MODES=${MODES}s
          fi
          #if [ x${VERIFIER:0:1} == "xy" -o x${VERIFIER:0:1} == "xY" ]; then
          #    MODES=${MODES}v
          #fi
          [ ! -z "$MODES" ] && MODES="-b $MODES"
          #========================================================
          DAEMON_OPTS="-d ${SIGNING_DOMAIN} -s ${KEYFILE} -S ${SELECTOR_NAME} ${MODES} -c ${CANON} -C ${REJECTION} ${EXTRA_ARGS}"
          


          Ну, и генерация ключиков
          #!/bin/bash
          
          if [ -z $1 ]; then
              echo "The domainname is not specified"
              exit 1
          fi
          
          PREFIX=/etc/mail
          #PREFIX=/opt/postfixadmin-hooks/mail
          
          # Location of OpenSSL binary (whereis -b openssl)
          OPENSSL=/usr/bin/openssl
          DOMAIN=$1
          USE_DKIM=1
          USE_DOMAINKEYS=1
          SELECTOR=mail
          MAILTMPFFILE=/tmp/mail-`date "+%s"`
          POSTMASTER="postmaster@example.com"
          ADMINMAIL="tech@example.com"
          SENDMAIL="/usr/sbin/sendmail"
          
          # Create the directories for keys
          if [ -d ${PREFIX}/.privatekeys/${DOMAIN} ]; then
              echo "Directory for private keys for the domain ${DOMAIN} exists."
              exit 1
          else
              mkdir --mode=755 -p ${PREFIX}/.privatekeys/${DOMAIN}
          fi
          
          if [ -d ${PREFIX}/.publickeys/${DOMAIN} ]; then
              echo "Directory for private keys for the domain ${DOMAIN} exists."
              exit 1
          else
              mkdir --mode=755 -p ${PREFIX}/.publickeys/${DOMAIN}
          fi
          
          if [[ ${USE_DKIM} -eq "1" || ${USE_DOMAINKEYS} -eq "1" ]]; then
              echo "Generating the private key for ${DOMAIN}"
              ${OPENSSL} genrsa -out ${PREFIX}/.privatekeys/${DOMAIN}/${SELECTOR} 1024 > /dev/null
              echo "Generating the public key for ${DOMAIN}"
              ${OPENSSL} rsa -in ${PREFIX}/.privatekeys/${DOMAIN}/${SELECTOR} -out ${PREFIX}/.publickeys/${DOMAIN}/${SELECTOR}.public.key -pubout -outform PEM > /dev/null
              # Prepare the key
              keydata=$(grep -v '^-' ${PREFIX}/.publickeys/${DOMAIN}/${SELECTOR}.public.key)
              pubkey=$(echo ${keydata} | sed 's/ //g')
          fi
          
          # Process dk-milter
          if [ ${USE_DKIM} -eq "1" ]; then
              if [ -d ${PREFIX}/opendkim/keys/${DOMAIN} ]; then
          	echo "DKIM directory for private keys for the domain ${DOMAIN} exists."
          	exit 1
              else
          	mkdir --mode=755 -p ${PREFIX}/opendkim/keys/${DOMAIN}
          	install --owner=opendkim --group=opendkim --mode=400 ${PREFIX}/.privatekeys/${DOMAIN}/${SELECTOR} ${PREFIX}/opendkim/keys/${DOMAIN}/${SELECTOR}
          	echo "${SELECTOR}._domainkey.${DOMAIN} ${DOMAIN}:${SELECTOR}:${PREFIX}/opendkim/keys/${DOMAIN}/${SELECTOR}" >> ${PREFIX}/opendkim/keyTable
          	echo "*@${DOMAIN} ${SELECTOR}._domainkey.${DOMAIN}" >> ${PREFIX}/opendkim/signingTable
          	/usr/sbin/service opendkim restart
              fi
          else
              echo "DKIM signature is not used. Skipping."
          fi
          
          # Process Domainkeys
          if [ ${USE_DOMAINKEYS} -eq "1" ]; then
              if [ -d ${PREFIX}/domainkeys/privatekeys/${DOMAIN} ]; then
          	echo "Domainkeys directory for private keys for the domain ${DOMAIN} exists."
          	exit 1
              else
          	mkdir --mode=755 -p ${PREFIX}/domainkeys/privatekeys/${DOMAIN}
          	install --owner=postfix --group=root --mode=400 ${PREFIX}/.privatekeys/${DOMAIN}/${SELECTOR} ${PREFIX}/domainkeys/privatekeys/${DOMAIN}/${SELECTOR}
          	echo "*@${DOMAIN}:${PREFIX}/domainkeys/privatekeys/${DOMAIN}/${SELECTOR}" >> ${PREFIX}/domainkeys/keys
          	echo "${DOMAIN}" >> ${PREFIX}/domainkeys/domains
          	/usr/sbin/service dk-filter restart
              fi
          else
              echo "Domainkeys signature is not used. Skipping."
          fi
          
          echo "From: Postfix Admin <${POSTMASTER}>" >> ${MAILTMPFFILE}
          echo "To: ${ADMINMAIL}" >> ${MAILTMPFFILE}
          echo "Subject: Domain ${DOMAIN} was added, DNS intervention is needed" >> ${MAILTMPFFILE}
          echo "MIME-Version: 1.0" >> ${MAILTMPFFILE}
          echo "Content-Type: text/plain; charset=us-ascii" >> ${MAILTMPFFILE}
          echo "X-Priority: 1" >> ${MAILTMPFFILE}
          echo "X-MSMail-Priority: High" >> ${MAILTMPFFILE}
          echo "" >> ${MAILTMPFFILE}
          echo "Publish these NS records:" >> ${MAILTMPFFILE}
          echo "_domainkey.${DOMAIN}              IN      TXT \"o=-\"" >> ${MAILTMPFFILE}
          echo "_adsp._domainkey.${DOMAIN}        IN      TXT \"dkim=all\"" >> ${MAILTMPFFILE}
          echo "${SELECTOR}._domainkey.${DOMAIN}  IN      TXT \"v=DKIM1; k=rsa; p=${pubkey}\"" >> ${MAILTMPFFILE}
          echo "" >> ${MAILTMPFFILE}
          echo "" >> ${MAILTMPFFILE}
          echo "" >> ${MAILTMPFFILE}
          echo "--" >> ${MAILTMPFFILE}
          echo "Thanks," >> ${MAILTMPFFILE}
          echo "Postfix Administrator                         mailto:${POSTMASTER}" >> ${MAILTMPFFILE}
          
          cat ${MAILTMPFFILE} | ${SENDMAIL} -f ${POSTMASTER} -t
          unlink ${MAILTMPFFILE}
          


          Связка работает с постфиксом, но двумя взмахами напильника можно приловчить на Exim тоже.
          Да, кстате… Товарищи, если шлёте почту на буржуев, то не забывайте про Feedback Loops:
          mail.live.com/mail/services.aspx#JMRPP
          feedbackloop.yahoo.net/
          fbl.hostedemail.com/
          postmaster.aol.com/SupportRequest.php
          feedback.comcast.net/

          После этого к вам начнут поступать отчёты в т.н. формате ARF (вложения типа message/feedback-report).
          Также немаловажно переваривать bounce-messages (т.е. отлупы о том, что письма не доставлены адресату по тем или иным причинам (не верно указан адрес получателя, на получателе закончилось место и т.д. — можно заюзать тот же перловский Mail::DeliveryStatus::BounceParser ).

          И сообщите им же, что вы хотите слать МНОГО почты:
          postmaster.aol.com/cgi-bin/whitelist/whitelist_guides.pl
          help.yahoo.com/l/us/yahoo/mail/postmaster/bulkv2.html
          • 0
            Порадовало, комментарий может потягаться с самой статьёй.
          • +2
            У вас exim сам подписывает сообщения, и никакой ему opendkim не нужен. В вашем случае, opendkim понадобился только для генерации ключей (хоть и можно было обойтись без него) Вот тут написано:
            As of version 4.70, Exim has native support for DKIM.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.