さくらのレンタルサーバでDKIM対応

さくらのレンタルサーバはメール送信ドメイン認証技術DKIM に対応してませんが、特定の条件下で送信されるメールのみ DKIM に対応できたので手順を公開します。(※RainLoop は適当にインストールしてください)

・仕組み

php.ini の sendmail_path を変更し、 php のmail関数で送信されるメールを自作のスクリプト経由で送信されるように変更する。スクリプトは、 “opendkimの起動→sendmailでメール送信→opendkimの停止”をする。さくらのレンタルサーバでは常駐プロセスの起動は禁止されているので、メール送信ごとに opendkimの起動/停止をするようにしている。

・条件

  1. 該当ドメインの TXT レコードに DKIM の公開鍵を登録できること
  2. メールクライアントは Webメールの RainLoop を さくらのレンタルサーバにインストールし、使用すること。
  3. RainLoop から送信するメールの Bcc は未対応。

1. OSと sendmail のバージョンの確認

#-- OSの確認
uname -a
FreeBSD www439.sakura.ne.jp 11.2-RELEASE-p9 FreeBSD 11.2-RELEASE-p9 #0: Tue Feb  5 15:30:36 UTC 2019     root@amd64-builder.daemonology.net:/usr/obj/usr/src/sys/GENERIC  amd64

#-- sendmail のバージョンの確認
sendmail -bt -d0.1 | grep Version
Version 8.15.2

2. opendkim の インストール

#-- opendkim のインストール
mkdir -p ~/mail/source
cd ~/mail/source

#-- opendkim-2.10.3.tar.gz をダウンロードし ~/mail/source/opendkim-2.10.3.tar.gz に 用意
tar xvzf opendkim-2.10.3.tar.gz
cd opendkim-2.10.3
./configure --prefix=${HOME}/mail/opendkim
make
make install
mkdir -p ~/mail/opendkim/{etc/key,run}

#-- 設定ファイルの用意
cd ~/mail/opendkim/etc/

DOMAIN=examle.com
SELECTOR=default

cat <<_EOF_> opendkim.conf
Mode             s
UserID           ${USER}:users
Socket           unix:/home/${USER}/mail/opendkim/run/opendkim.socket
Umask            002
SoftwareHeader   no
Canonicalization relaxed/simple
Domain           ${DOMAIN}
Selector         ${SELECTOR}
MinimumKeyBits   1024
KeyFile          /home/${USER}/mail/opendkim/etc/key/${SELECTOR}.private
PidFile          /home/${USER}/mail/opendkim/run/opendkim.pid
_EOF_

#-- 秘密鍵、公開鍵の作成
~/mail/opendkim/sbin/opendkim-genkey -D ~/mail/opendkim/etc/key -d ${DOMAIN} -s ${SELECTOR}

#-- default.txt の内容を DNSに登録する
cat key/default.txt
default._domainkey	IN	TXT	( "v=DKIM1; k=rsa; "
	  "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC82amdPQIlaS2HWexxjqNcTURhGtPWUIbWG1G+IpZySPSdO6vuDJtLiZx3fAAMt2pF7FXk6rN91YobbD9PU0TOWhe5ovFiJEggRWj3OGZlScSOBn7fHpd6Cbh1OuqwSVbQlDf6dGgMXIPloYlwGry8W3NLxVI4NBw5xQ9VNT3GLwIDAQAB" )  ; ----- DKIM key default for examle.com


3. submit.cf の作成

#-- opendkim を使用してメールを送信する為の設定ファイル(submit.cf)を作成
cd ~/mail
mkdir mqueue tmp log
cp -pr /usr/share/sendmail/cf .
cd cf/cf

#-- submit_tmp.mc を editor で作成
vi submit_tmp.mc
#-- ここから
divert(0)dnl
VERSIONID(`$Id: submit.mc,v 8.15 2019-06-22 13:12:46 ca Exp $')
define(`confCF_VERSION', `Submit')dnl
define(`__OSTYPE__',`')dnl dirty hack to keep proto.m4 from complaining
define(`_USE_DECNET_SYNTAX_', `1')dnl support DECnet
define(`confTIME_ZONE', `USE_TZ')dnl
define(`confDONT_INIT_GROUPS', `True')dnl
define(`confRUN_AS_USER', `_USER_')dnl
define(`confTRUSTED_USER',`_USER_')dnl
define(`confTRUSTED_USERS',`_USER_')dnl
define(`MSP_QUEUE_DIR',`/home/_USER_/mail/mqueue')dnl
define(`STATUS_FILE', `/home/_USER_/mail/sm-client.st')dnl
define(`confPID_FILE',`/home/_USER_/mail/sm-client.pid')dnl
define(`confRECEIVED_HEADER', `$?sfrom $s $.$?_
	$.$?{auth_type}(authenticated$?{auth_ssf} (${auth_ssf} bits)$.)
	$.by $j $?r with $r$. id $i$?{tls_version}
	(using ${tls_version} with cipher ${cipher} (${cipher_bits} bits) verified ${verify})$.$?u
	for $u; $|;
	$.$b')dnl
INPUT_MAIL_FILTER(`opendkim',`S=unix:/home/_USER_/mail/opendkim/run/opendkim.socket')dnl
FEATURE(`msp', `[127.0.0.1]')dnl
#-- ここまで

#-- submit.cf を作成
sed "s/_USER_/${USER}/" submit_tmp.mc > submit.mc
make submit.cf
cp -p submit.cf ~/mail/

4. sendmail.sh を作成

cd ~/mail

vi sendmail_tmp.sh
#-- ここから
#!/bin/sh

DIR=/home/_USER_/mail
DEBUG=${DIR}/log/debug.log

(
OPENDKIM_PID=${DIR}/opendkim/run/opendkim.pid
OPENDKIM_BIN=${DIR}/opendkim/sbin/opendkim

#-- remove old mail data
find ${DIR}/tmp -mtime +7 -type f -exec rm {} \;

#-- check opendkim already working
while :
do
  if [ -f "${OPENDKIM_PID}" ]
  then
    if [ $(stat -f %m ${OPENDKIM_PID}) -ge $(($(date +%s)+10)) ]
    then
      break
    else
      sleep 5
    fi
  else
    break
  fi
done

#-- save mail data
UNIXTIME=$(date +%s)
TMP=${DIR}/tmp/T${UNIXTIME}-$$.txt
cat - >> ${TMP}

#-- start opendkim
${OPENDKIM_BIN}

#-- create mail queue
QUEUE=${DIR}/tmp/Q${UNIXTIME}-$$.txt

printf "HELO $(hostname)\r\n" > ${QUEUE}
FROM=$(awk '/^From: /{print $2}' ${TMP} | head -1 )
printf "MAIL FROM: ${FROM}\r\n" >> ${QUEUE}
cat ${TMP} | perl -ne 'BEGIN{$read=0;}{if(/^To:|^Cc:/){ $read=1; } elsif (/^\w|^$/){ $read=0;}
if ($read==1) { s/(^\w+:|\s)//g; push @address,(split /,/);}END{foreach (@address) { print "RCPT TO: $_\r\n"} print "DATA\r\n";}}' >> ${QUEUE}
cat ${TMP} >> ${QUEUE}
printf ".\r\n" >> ${QUEUE}

#-- send email
cat ${QUEUE} | /usr/sbin/sendmail -C${DIR}/submit.cf -v -i -t -bs
sleep 3

#-- stop opendkim
kill $(cat ${OPENDKIM_PID}) && rm -f ${OPENDKIM_PID}

) > ${DEBUG} 2>&1
#-- ここまで

sed "s/_USER_/${USER}/" sendmail_tmp.sh > sendmail.sh

chmod 755 sendmail.sh
rm -f sendmail_tmp.sh

5. php.ini の設定変更

cat <<"_EOF_"> /home/${USER}/www/php.ini
sendmail_path = /home/${USER}/mail/sendmail.sh
_EOF_

6. RainLoop の SMTP 設定変更

RainLoop の ドメイン設定にて “PHP mail() 関数を使用する  (beta)” を有効にする

これで RainLoop から送信するメール(To,Cc) が DKIM署名される。

メールリレーサーバの構築[さくらのクラウド/CentOS7]

GMOの特定のNWが Gmail/Gsuite に嫌われているらしく、メール配送に苦慮している方がいるようなので、さくらのクラウドにメールリレーサーバを構築する手順を作成しました。一時的な逃げ道になればいいかと思います。

既存のメールサーバがSPFに対応していることが前提で、DKIM、DMARCに対応していても問題は起きないはずです。さくらのVPSでも同様に構築できるかと思います。さくらのVPSで構築する場合は広告から申し込んで頂けると幸いです。

1. メールリレーサーバのセットアップ

#-- まずは さくらのクラウドで CentOS7 でサーバを作成
#-- ホスト名は、relay.example.com とする

#-- 既存の送信用メールサーバから送信される Envelope From のドメインを変数に設定(例では2つ)
DOMAINS="xxx.example.com yyy.example.com"

#-- 既存の送信用メールサーバのIPアドレスを変数に設定(例では2サーバ)
IPS="xxx.xxx.xxx.xxx yyy.yyy.yyy.yyy"

#-- tool のインストールとパッケージを更新
yum install -y bind-utils
yum update -y

#-- IPADDRESS の取得
source /etc/sysconfig/network-scripts/ifcfg-eth0

#-- postfix の設定

#-- From に指定できるドメインを設定
for x in ${DOMAINS}
do
  echo "${x} OK" >> /etc/postfix/access
done
postmap /etc/postfix/access

POSTCONF="postconf -c /etc/postfix -e"
${POSTCONF} inet_interfaces=${IPADDR}
${POSTCONF} smtpd_helo_restrictions="reject_invalid_hostname reject_non_fqdn_hostname reject_unknown_hostname"
${POSTCONF} smtpd_sender_restrictions="reject_non_fqdn_sender reject_unknown_sender_domain"
${POSTCONF} inet_protocols=ipv4
${POSTCONF} smtpd_helo_required=yes
${POSTCONF} message_size_limit=52428800
${POSTCONF} disable_vrfy_command=yes
${POSTCONF} smtpd_discard_ehlo_keywords=dsn,enhancedstatuscodes,etrn
${POSTCONF} smtpd_sender_restrictions="check_sender_access hash:/etc/postfix/access, reject"
${POSTCONF} smtp_tls_loglevel=1
${POSTCONF} smtp_tls_security_level=may
${POSTCONF} smtp_use_tls=yes
${POSTCONF} smtp_tls_CAfile=/etc/pki/tls/certs/ca-bundle.crt
${POSTCONF} tls_random_source=dev:/dev/urandom

MYNETWORKS="127.0.0.1,${IPADDR}"
for x in ${IPS}
do
  MYNETWORKS="${MYNETWORKS},${x}"
done
MYNETWORKS=$(echo ${MYNETWORKS} | sed 's/,$//')
${POSTCONF} mynetworks=${MYNETWORKS}

#-- firewalld で既存のメールサーバからのアクセスを許可
for x in ${IPS}
do
  firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='${x}/32' accept"
done

#-- postfix, firewalld の設定変更も兼ねてOSを再起動
shutdown -r now

2. DNS登録 (事前にSPFのTTLは短くしておくこと)

#-- relay.example.com の 正引き(Aレコード)をDNSに登録する
#-- 作成したリレーサーバのIPアドレスの逆引き(PTRレコード) に relay.example.com. を登録する
#-- xxx.example.com と yyy.example.com の SPFを記述したTXTレコードに作成したリレーサーバのIPアドレスを追加する

3. DNSの登録確認

#-- python3.4 インストール
yum install -y python34 python34-pip
pip3.4 install py3dns pyspf

#-- SPF登録確認ツールの作成
cat <<-'_EOL_'> check_spf.py
#!/usr/bin/python3.4
import sys,spf
print(spf.check(i=sys.argv[1],s=sys.argv[2],h="localhost")[0])
_EOL_
chmod 755 check_spf.py

#-- 確認コマンド実行
source /etc/sysconfig/network-scripts/ifcfg-eth0

#-- From アドレスに使用する自メールドメインを変数に設定
DOMAINS="xxx.example.com yyy.example.com"

while :
do
  FLAG=0
  if [ $(dig $(hostname). a +short | grep -c "^${IPADDR}$") -eq 0 ]
  then
    echo "$(hostname) の A レコードが ${IPADDR} と一致していません"
    FLAG=$(($FLAG + 1))
  fi

  if [ $(dig -x ${IPADDR} +short | grep -c "^$(hostname).$") -eq 0 ]
  then
    echo "${IPADDR} の PTR レコードが $(hostname) と一致していません"
    FLAG=$(($FLAG + 1))
  fi

  for x in ${DOMAINS}
  do
    if [ $(./check_spf.py ${IPADDR} ${x}) != "pass" ]
    then
      echo "${IPADDR} が ${x} の SPF レコードに登録されていません"
      FLAG=$(($FLAG + 1))
    fi
  done

  if [ ${FLAG} -eq 0 ]
  then
    break
  fi

  sleep 30

done
#-- 上記スクリプトが終了したら、確認完了

4. 既存のメールサーバのメール配送先を構築したリレーサーバに変更する

#-- postfix であれば relayhost に構築したメールサーバの relay.example.com を設定
#-- sendmail であれば SmartRealy に構築したメールサーバの relay.example.com を設定