さくらのクラウドにCentOS8(CentOS Stream 8)でメールサーバを構築する

サーバスペック

仮想コア: 2core, メモリ 2GB, ディスク SSD 20GB

  • ウィルスチェック用途の clamd が メモリを消費するので 2GB を選択
  • ディスクは足りなくなったら追加すればいいので 20GB を選択
  • バックアップは自動バックアップ

ホスト名とドメイン名

  • ホスト名は mail.sacloud.ma3ki.net とします。
  • ドメイン名は sacloud.ma3ki.net とします。

メールサーバの機能

  • 送信機能 (SMTP-OUTBOUND)
    • SMTP Submission(587/tcp)
    • STARTTLS
    • SMTP over TLS(465/tcp)
    • SMTP-AUTH
    • Virus Check (パスワード付きZIPファイルは送信拒否)
    • 各種メール認証技術
      • SPF, DKIM, DMARC, ARC 署名
  • 受信機能 (SMTP-INBOUND)
    • SMTP(25/tcp)
    • STARTTLS
    • Virus Check (パスワード付きZIPファイルは受信拒否)
    • Spam Check
    • 各種メール認証技術
      • SPF, DKIM, DMARC, ARC 認証
  • 参照機能
    • POP over TLS(995/tcp)
    • IMAP over TLS(993/tcp)
  • Webmail
    • Roundcube
      • 各種 Plugin機能
        • フィルタリング (managesieve)
        • パスワード変更 (password)
  • アカウント管理
    • phpldapadmin
      • メールアドレス追加/削除/停止
      • パスワード変更
    • マルチドメイン対応可能
    • Thunderbird の Autoconfig に対応
    • MTA-STS に対応
    • SMTP/POP/IMAP の辞書アタックへの ratelimit
    • メールアーカイブ機能

構築するメールサーバはシングル構成です。全ての機能を1台で提供します。

インストールアプリケーション

  • certbot
  • clamd
  • rspamd
  • redis
  • 389-ds
  • dovecot
  • postfix
  • nginx
  • php-fpm
  • mysql
  • phpldapadmin
  • roundcube

メールシステム通信図メールシステム通信図

セットアップ手順は次の投稿からです。

CentOS Stream 8 でセットアップする場合

基本的には CentOS8 のセットアップ手順で問題ないが、下記の2点のみ読み替える必要があります。

01.メールサーバ構築 – 準備編[さくらのクラウド/CentOS8] の

# dnf config-manager --set-enabled PowerTool
を
# dnf config-manager --set-enabled powertools
に変更

03.メールサーバ構築 – ユーザDB編[さくらのクラウド/CentOS8] の

# sed -ri "s/;(root_dn).*/\1=${ROOT_DN}/;s/;(root_password).*/\1=${ROOT_PASSWORD}/" ${WORKDIR}/389ds
を
# sed -ri "s/;(root_password).*/\1=${ROOT_PASSWORD}\nroot_dn=${ROOT_DN}/" ${WORKDIR}/389ds
に変更

CentOS8 セットアップメモ[さくらのVPS/CentOS8]

さくらのVPSにCentOS-8-x86_64-1905-dvd1.iso で minimal インストールした後にメールサーバを構築するための自分用のメモ。

・ネットワーク設定

#-- IPとDNS設定
IPV4_ADDR=X.X.X.X
IPV4_SUBNET=23
IPV4_GATEWAY=X.X.X.X
IPV4_DNS=8.8.8.8
IPV6_ADDR=X:X:X:X:X:X:X:X
IPV6_SUBNET=64
IPV6_GATEWAY=fe80::1
IPV6_DNS=2001:e42::1

#-- ipv4 の設定
nmcli connection modify ens3 connection.autoconnect yes ipv4.address ${IPV4_ADDR}/${IPV4_SUBNET} ipv4.gateway ${IPV4_GATEWAY} ipv4.method manual ipv4.dns ${IPV4_DNS}

#-- ipv6 の設定
nmcli connection modify ens3 ipv6.address ${IPV6_ADDR}/${IPV6_SUBNET} ipv6.gateway ${IPV6_GATEWAY} ipv6.method manual ipv6.dns ${IPV6_DNS}

#-- 設定反映
(nmcli c down ens3; nmcli c up ens3)

#-- dnfのwarning 抑制
LANG=C export LANG

#-- 必要なものをインストール epel-release
dnf install -y bash-completion telnet bind-utils git tar epel-release

#-- selinux 無効化
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
setenforce 0

・rspamd のインストール (dnf でまだインストールできない)

#-- rspamd
mkdir -p ~/work/git && cd ~/work/git
dnf install -y cmake gcc gcc-c++ make autoconf automake libtool
dnf install -y {luajit,sqlite,openssl,libevent,glib2,libicu,libsodium}-devel

curl -O http://www.colm.net/files/colm/colm-0.13.0.7.tar.gz
tar xvzf colm-0.13.0.7.tar.gz
cd colm-0.13.0.7/
./configure
make
make install

curl -O http://www.colm.net/files/ragel/ragel-7.0.0.12.tar.gz
tar xvzf ragel-7.0.0.12.tar.gz
cd ragel-7.0.0.12
./configure
make
make install

git clone --recursive https://github.com/vstakhov/rspamd.git
mkdir rspamd.build && cd rspamd.build
cmake ../rspamd
make
make install

cat <<'_EOF_'>> /usr/lib/systemd/system/rspamd.service
[Unit]
Description=rapid spam filtering system
After=nss-lookup.target network-online.target
Documentation=https://rspamd.com/doc/

[Service]
LimitNOFILE=1048576
NonBlocking=true
ExecStart=/usr/local/bin/rspamd -c /etc/rspamd/rspamd.conf -f
ExecReload=/bin/kill -HUP $MAINPID
User=_rspamd
RuntimeDirectory=rspamd
RuntimeDirectoryMode=0755
Restart=always

[Install]
WantedBy=multi-user.target
_EOF_

ln -s /usr/local/etc/rspamd /etc/rspamd

#-- userid,gid,home,shell は手動で設定したほうがいい
useradd _rspamd

#-- ここで設定とかする

mkdir /var/log/rspamd && chown _rspamd. /var/log/rspamd
systemctl enable rspamd
systemctl start rspamd

・roundcube のインストールメモ

#-- roundcube のインストール
cd ~/work/git
git clone https://github.com/roundcube/roundcubemail.git
cd ~/work/git/roundcubemail
VERSION=$(git for-each-ref --sort=-taggerdate --format='%(tag)' refs/tags | grep -m 1 "1\.4-")
git checkout ${VERSION}
HTTPS_DOCROOT=/var/www/html/https_root
cp -pr ../roundcubemail ${HTTPS_DOCROOT}/roundcubemail-${VERSION}
ln -s ${HTTPS_DOCROOT}/roundcubemail-${VERSION} ${HTTPS_DOCROOT}/roundcube

#-- roundcube の DB を作成
mysql -e "create database roundcubemail character set utf8 collate utf8_bin;"
#-- database の password は お好みで
mysql -e "create user roundcube@localhost identified by 'roundcube';"
mysql -e "grant all privileges ON roundcube.* TO roundcube@localhost ;"
mysql -e "flush privileges;"
mysql roundcubemail < ${HTTPS_DOCROOT}/roundcube/SQL/mysql.initial.sql

yum install -y php-{pdo,xml,pear,mbstring,intl,gd,mysqlnd,ldap,json,pecl-zip} 
dnf install -y php-pear-Auth-SASL php-pear-Net-SMTP
pear install Net_IDNA2-0.2.0
pear install Mail_Mime-1.10.3

#-- ここで設定とかする

dnf install -y npm
npm install -g less

#-- php は 7.2.11, mysql は 8.0.13

他のアプリケーションは dnf でインストールできるのでCentOS7の手順で問題ないはず

OpenLDAPのミラーモード[さくらのVPS/CentOS7]

OpenLDAPのミラーモードを2台のサーバで構築する手順。2台のOpenLDAPサーバに同時に更新を行うと問題が発生する場合がある為、keepalived で VIPを使用できるように設定する。セットアップは 1台目と2台目のサーバで全く同じコマンドを実行する。

・環境

Network InterfaceIPアドレス
1台目:eth1192.168.0.11/24
2台目:eth1192.168.0.12/24
VIP192.168.0.10/24

1. firewalld のセットアップ

#-- eth1 への IPアドレスは事前に設定してあること
#-- eth1 への通信を許可
firewall-cmd --add-interface=eth1 --permanent --zone=trusted 
firewall-cmd --reload

2. keepalived のセットアップ

#-- keepalived のインストール
yum install -y keepalived
systemctl enable keepalived

#-- VIPの設定
cat <<_EOF_> /etc/keepalived/keepalived.conf
vrrp_sync_group VG1 {
  group {
    SERVICE
  }
}

vrrp_instance SERVICE {
  state BACKUP
  interface eth1
  virtual_router_id 1
  priority 100
  advert_int 5
  virtual_ipaddress {
    192.168.0.10
  }
}
_EOF_

#-- keepalived の起動
systemctl start keepalived

#-- これで VIP がどちらかのサーバに設定される

3. openldap のセットアップ

#-- rsyslog に slapd のログ出力設定を追加
echo "local4.*  /var/log/openldaplog" > /etc/rsyslog.d/openldap.conf
sed -i '1s#^#/var/log/openldaplog\n#' /etc/logrotate.d/syslog
systemctl restart rsyslog

#-- パスワードを変数にセット
ROOT_PASSWORD="YourPassword"
ROOT_PW=$(slappasswd -s ${ROOT_PASSWORD})

#-- openldap のインストール (sendmail.schema が必要ない場合、sendmail-cf はインストールしない)
yum install -y openldap-servers openldap-clients sendmail-cf

#-- sendmail schema を保存
cp -p /usr/share/sendmail-cf/sendmail.schema /etc/openldap/schema/

#-- slapd.conf を作成 (schemaは、ミラーモードに 必要なもの + sendmail.schema のみ有効) 
cat <<_EOL_> /etc/openldap/slapd.conf
loglevel 256
sizelimit -1
#include  /etc/openldap/schema/corba.schema
#include  /etc/openldap/schema/duaconf.schema
#include  /etc/openldap/schema/java.schema
#include  /etc/openldap/schema/nis.schema
#include  /etc/openldap/schema/ppolicy.schema
#include  /etc/openldap/schema/collective.schema
include  /etc/openldap/schema/core.schema
include  /etc/openldap/schema/cosine.schema
include  /etc/openldap/schema/dyngroup.schema
include  /etc/openldap/schema/inetorgperson.schema
include  /etc/openldap/schema/misc.schema
include  /etc/openldap/schema/openldap.schema
include  /etc/openldap/schema/sendmail.schema
allow bind_v2
pidfile  /var/run/openldap/slapd.pid
argsfile /var/run/openldap/slapd.args
moduleload syncprov.la
moduleload back_hdb.la
backend hdb
#-- config 用データベースに対するアクセス制限
database config
rootdn  "cn=config"
rootpw  "${ROOT_PW}"
access to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
    by dn="cn=manager" manage
    by * none
overlay syncprov
#-- monitor データベースに対するアクセス制限
#   ROOT_DN のみ read 可能
#   確認コマンド ldapsearch -x -D "cn=config" -W +
database monitor
access to *
    by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" read
    by * none
#-- user データベース
database hdb
suffix  ""
rootdn  "cn=manager"
rootpw  "${ROOT_PW}"
#--- userPassword に対するアクセス制限
#   自分自身は書き換え可能
#   ROOT_DN は書き換え可能
#   匿名接続ならば、認証したときにこの属性が使える
access to attrs=UserPassword
    by self write
    by anonymous auth
    by * none
#-- Termed は ROOT_DN のみ書き換え可能
access to dn.regex="uid=.*,ou=Termed,dc=.*"
    by * none
#-- その他の属性は ROOT_DN のみ書き換え可能
#   ROOT_DN 以外は read のみ可能
access to *
    by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
    by * read
checkpoint 1024 15
directory /var/lib/ldap
index entryCSN,entryUUID eq
index objectClass   eq,pres
index uid           eq,pres,sub
index mailHost,mailRoutingAddress,mailLocalAddress,sendmailMTAHost eq,pres
overlay syncprov
_EOL_

#-- slapd.conf から olc に設定を変換する為の前処理
rm -rf /etc/openldap/slapd.d/* /var/lib/ldap/*
mv /etc/openldap/slapd.d /etc/openldap/slapd.d.org

#-- DB_CONFIGの設定
# cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG
cat <<'_EOL_'> /var/lib/ldap/DB_CONFIG
set_cachesize 0 10485760 1
set_flags DB_LOG_AUTOREMOVE
set_lg_regionmax 262144
set_lg_bsize 2097152
_EOL_

#-- DBの初期化など
systemctl start slapd
systemctl stop slapd
mv /etc/openldap/slapd.d.org /etc/openldap/slapd.d

#-- slapd.conf から olc に設定を変換
sudo -u ldap slaptest -f /etc/openldap/slapd.conf -F /etc/openldap/slapd.d

#-- slapd の起動
systemctl enable slapd
systemctl start slapd

#-- ミラーモードの ldif 作成
cat <<_EOL_> /tmp/ldap_mirror.ldif
dn: cn=config
changetype: modify
add: olcServerID
olcServerID: 001 ldap://192.168.0.11
-
add: olcServerID
olcServerID: 002 ldap://192.168.0.12

dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcSyncRepl
olcSyncRepl: rid=001 provider=ldap://192.168.0.12/ binddn="cn=manager" bindmethod=simple credentials=${ROOT_PASSWORD} searchbase="" type=refreshAndPersist retry="5 10 60 +" timeout=1 scope=sub starttls=no schemachecking=ok
olcSyncRepl: rid=002 provider=ldap://192.168.0.11/ binddn="cn=manager" bindmethod=simple credentials=${ROOT_PASSWORD} searchbase="" type=refreshAndPersist retry="5 10 60 +" timeout=1 scope=sub starttls=no schemachecking=ok
-
add: olcMirrorMode
olcMirrorMode: TRUE

dn: olcDatabase={0}config,cn=config
changetype: modify
add: olcSyncRepl
olcSyncRepl: rid=001 provider=ldap://192.168.0.12/ binddn="cn=config" bindmethod=simple credentials=${ROOT_PASSWORD} searchbase="cn=config" type=refreshAndPersist retry="5 10 300 +" timeout=1
olcSyncRepl: rid=002 provider=ldap://192.168.0.11/ binddn="cn=config" bindmethod=simple credentials=${ROOT_PASSWORD} searchbase="cn=config" type=refreshAndPersist retry="5 10 300 +" timeout=1
-
add: olcMirrorMode
olcMirrorMode: TRUE
_EOL_

#-- ミラーモードの設定投入
ldapadd -D "cn=config" -w ${ROOT_PASSWORD} -f /tmp/ldap_mirror.ldif

# これで VIP に対して更新処理を行えば、もう一方のサーバにも更新内容が反映される

さくらのレンタルサーバで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 を設定

roundcube を 1.3.8 から 1.4-rc1 へバージョンアップする

#-- 変数の設定
DOMAIN=masdon.life
HTTPS_DOCROOT=/var/www/html/https_root

cd /root/work/git/roundcubemail

#-- git pull で更新
git pull
VERSION=1.4-rc1
git checkout ${VERSION}
cp -pr ../roundcubemail ${HTTPS_DOCROOT}/roundcubemail-${VERSION}
ln -s ${HTTPS_DOCROOT}/roundcubemail-${VERSION} ${HTTPS_DOCROOT}/roundcube

#-- 設定

cat <<'_EOF_'> ${HTTPS_DOCROOT}/roundcube/config/config.inc.php
<?php
$config['db_dsnw'] = 'mysql://roundcube:roundcube@localhost/roundcubemail';
$config['default_host'] = array('_DOMAIN_');
$config['default_port'] = 993;
$config['smtp_server'] = '_DOMAIN_';
$config['smtp_port'] = 465;
$config['smtp_user'] = '%u';
$config['smtp_pass'] = '%p';
$config['support_url'] = '';
$config['product_name'] = 'Roundcube Webmail';
$config['des_key'] = 'rcmail-!24ByteDESkey*Str';
$config['plugins'] = array('managesieve', 'password', 'archive', 'zipdownload');
$config['managesieve_host'] = 'localhost';
$config['spellcheck_engine'] = 'pspell';
$config['skin'] = 'larry';
_EOF_

sed -i "s#_DOMAIN_#ssl://${DOMAIN}#" ${HTTPS_DOCROOT}/roundcube/config/config.inc.php

cp -p ${HTTPS_DOCROOT}/roundcube/plugins/managesieve/config.inc.php{.dist,}
sed -i -e "s/managesieve_vacation'] = 0/managesieve_vacation'] = 1/" ${HTTPS_DOCROOT}/roundcube/plugins/managesieve/config.inc.php

cp -p ${HTTPS_DOCROOT}/roundcube/plugins/password/config.inc.php{.dist,}

sed -i -e "s/'sql'/'ldap'/" \
       -e "s/'ou=people,dc=example,dc=com'/''/" \
       -e "s/'dc=exemple,dc=com'/''/" \
       -e "s/'uid=%login,ou=people,dc=exemple,dc=com'/'uid=%name,ou=People,%dc'/" \
       -e "s/'(uid=%login)'/'(uid=%name,ou=People,%dc)'/" ${HTTPS_DOCROOT}/roundcube/plugins/password/config.inc.php

chown -R nginx. ${HTTPS_DOCROOT}/roundcubemail-${VERSION}
cd ${HTTPS_DOCROOT}/roundcube/bin
./install-jsdeps.sh

#-- database などの update (./update.sh の参照している php が module をインストールした php のバージョンと一致していること)
./update.sh
What version are you upgrading from? Type '?' if you don't know.
1.3.8
WARNING: Dependency check failed!
(Some of your configuration settings require other options to be configured or additional PHP modules to be installed)
- spellcheck_engine: This requires the <tt>pspell</tt> extension which could not be loaded.
Please fix your config files and run this script again!
See ya.
Executing database schema update.
Updating database schema (2018021600)... [OK]
Updating database schema (2018122300)... [OK]
This instance of Roundcube is up-to-date.
Have fun!

mv ${HTTPS_DOCROOT}/roundcube/installer ${HTTPS_DOCROOT}/roundcube/_installer

#-- elastic テーマを使用するため、lessc コマンドをインストール
yum install -y npm
npm install -g less

cd ${HTTPS_DOCROOT}/roundcube/skins/elastic
lessc -x styles/styles.less > styles/styles.css
lessc -x styles/print.less > styles/print.css
lessc -x styles/embed.less > styles/embed.css

28. さくらのレンタルサーバをマウントする – curlftpfs [さくらのVPS/CentOS7]

  • さくらのレンタルサーバ スタンダードプラン の 100GB ディスクをさくらのVPS でマウントして使用する
#-- 変数に値を代入
DOMAIN=example.sakura.ne.jp #-- レンタルサーバのドメイン
USER=foo #-- レンタルサーバのログインID
PASS=bar #-- レンタルサーバのパスワード

#-- curlftpfs のインストール
yum install -y curlftpfs

#-- mount ポイントの作成
mkdir -p /mnt/ftpfs

#-- ログイン情報ファイルを作成
cat  <<_EOL_> ~/.netrc
machine ${DOMAIN} login ${USER} password ${PASS}
_EOL_

#-- mount コマンドでマウントできるように fstab に記述
cat <<_EOL_>> /etc/fstab
curlftpfs#${DOMAIN} /mnt/ftpfs fuse noauto,user,rw,uid=0,allow_other 0 0
_EOL_
#- auto マウントを設定すると 再起動時にブートに失敗する

#-- マウント
mount /mnt/ftpfs

#-- 確認
df -h /mnt/ftpfs
Filesystem                           Size  Used Avail Use% Mounted on
curlftpfs#ftp://example.sakura.ne.jp/  954G     0  954G   0% /mnt/ftpfs

dovecot で public mailbox を使用する

#-- 変数に必要な値を代入
DOMAIN=masdon.life

#-- /etc/dovecot/local.conf に設定を追加する
vi /etc/dovecot/local.conf

#- mail_plugins に acl を追加
mail_plugins = $mail_plugins zlib acl

#- plugin に acl = vfile を追加
plugin {
  ...
  ...
  acl = vfile
}

#- protocol imap の mail_plugins に imap_acl を追加
protocol imap {
  mail_plugins = $mail_plugins imap_acl
  ...
}

#- namespace の設定を追加
namespace inbox {
  type = private
  separator = /
  prefix =
  inbox = yes
}

#- _Public/Test01 と _Public/Test02 を作成する
namespace {
  type = public
  separator = /
  prefix = _Public/
  location = maildir:/var/dovecot/public/%Ld
  subscriptions = yes
  list = yes
  mailbox Test01 {
    auto = subscribe
  }
  mailbox Test02 {
    auto = subscribe
  }
}

#-- dovecot を reload
systemctl reload dovecot

#-- _Public を該当ドメイン全てのユーザで使用できるよう acl を設定
echo "anyone eilprwts" > /var/dovecot/public/${DOMAIN}/dovecot-acl
chown dovecot. /var/dovecot/public/${DOMAIN}/dovecot-acl
chmod 600 /var/dovecot/public/${DOMAIN}/dovecot-acl

#-- 該当ドメインのメールアカウントを設定したメーラーで _Public/Test01と_Public/Test02 フォルダを購読しアクセスすると MBOXが作成される
#-- 作成されたMBOXは、上位フォルダの ACL が設定される
ls -ld /var/dovecot/public/${DOMAIN}/.Test0{1,2}
drwx------ 5 dovecot dovecot 146  2月 17 21:45 /var/dovecot/public/masdon.life/.Test01
drwx------ 5 dovecot dovecot 146  2月 17 21:09 /var/dovecot/public/masdon.life/.Test02

#-- _Public/Test01 を該当ドメイン全てのユーザで使用できるよう acl を設定
echo "anyone eilprwts" > /var/dovecot/public/${DOMAIN}/.Test01/dovecot-acl
chown dovecot. /var/dovecot/public/${DOMAIN}/.Test01/dovecot-acl
chmod 600 /var/dovecot/public/${DOMAIN}/.Test01/dovecot-acl

#-- _Public/Test02 をadminユーザのみで使用できるよう acl を設定
echo "user=admin@${DOMAIN} eilprwts" > /var/dovecot/public/${DOMAIN}/.Test02/dovecot-acl
chown dovecot. /var/dovecot/public/${DOMAIN}/.Test02/dovecot-acl
chmod 600 /var/dovecot/public/${DOMAIN}/.Test02/dovecot-acl

#-- _Public/Test02 にコマンドで acl に特定のユーザの権限を追加
doveadm acl add -u admin@${DOMAIN} _Public/Test02 user=ma3ki@${DOMAIN} expunge insert lookup post read write write-deleted write-seen

#-- _Public/Test02 にコマンドで acl に gr1 というグループの権限を追加
doveadm acl add -u admin@${DOMAIN} _Public/Test02 group=gr1 expunge insert lookup post read write write-deleted write-seen

#-- 権限の確認
doveadm acl get -u admin@${DOMAIN} _Public/Test01
ID     Global Rights
anyone        expunge insert lookup post read write write-deleted write-seen

doveadm acl get -u admin@${DOMAIN} _Public/Test02
ID                        Global Rights
user=admin@masdon.life        expunge insert lookup post read write write-deleted write-seen
user=ma3ki@masdon.life        expunge insert lookup post read write write-deleted write-seen

#-- ACL のフラグの意味 https://doc.dovecot.org/settings/plugin/acl/

27. メーリングリストサーバ構築 – sympa [さくらのVPS/CentOS7]

・ 構築の前に

  1. メールサーバの構築が 11. メールサーバ構築(5) – nginx/php-fpm [さくらのVPS/CentOS7]
    まで完了していること
  2. メーリングリスト用にサブドメインを一つ用意すること
  3. メーリングリストから送信されるシステムメールは SPF, DKIM, DMARC, ARC に対応
  4. メーリングリストから送信される投稿メールは SPF,ARC に対応し、DKIM, DMARCは自メールドメインからの投稿メールのみ対応する

・sympa のインストール

#-- 変数に必要な値を代入
DOMAIN=masdon.life
MLDOMAIN=ml.${DOMAIN}
LISTMASTER=admin@${DOMAIN}
URL=https://${DOMAIN}/sympa

#-- sympa のリポジトリ設定とsympaのインストール(副作用でnginxもインストールされてしまう)
curl -o /etc/yum.repos.d/sympa-ja.org.rhel.repo http://sympa-ja.org/download/rhel/sympa-ja.org.rhel.repo
yum install -y sympa sympa-nginx

#-- nginx の起動スクリプトの修正
sed -i -e 's#/usr/sbin/nginx#/usr/local/nginx/sbin/nginx#' \
       -e '/^PIDFile/a ExecStartPre=/usr/local/bin/ipv6upchk.sh nginx' /usr/lib/systemd/system/nginx.service

systemctl daemon-reload
systemctl enable nginx

#-- nginx と sympa-nginx を update しないように変更
echo "exclude=nginx*,sympa-nginx" >> /etc/yum.conf

#-- sympa の database を作成
mysql -e "CREATE DATABASE sympa CHARACTER SET utf8;"
#-- database の password は お好みで
mysql -e "GRANT ALL PRIVILEGES ON sympa.* TO 'sympa'@'localhost' IDENTIFIED BY 'sympass';"
mysql -e "FLUSH PRIVILEGES;"

#-- sympa の設定
cp -p /etc/sympa/sympa.conf{,.org}

sed -i -e "/^#domain/a domain ${MLDOMAIN}" \
       -e "/^#listmaster/a listmaster ${LISTMASTER}" \
       -e "/^#lang/a lang ja" \
       -e "/^#db_type/a db_type MySQL" \
       -e "/^#db_name/a db_name sympa" \
       -e "/^#db_host/a db_host 127.0.0.1" \
       -e "/^#db_user/a db_user sympa" \
       -e "/^#db_passwd/a db_passwd sympass" \
       -e "/^#wwsympa_url/a wwsympa_url ${URL}" /etc/sympa/sympa.conf

#- sympa の設定の確認(何も表示されなければOK)
sympa.pl --health_check

#- 定期的に postfix の設定を更新するスクリプトを作成
cat <<'_EOL_'> /usr/local/bin/create_sympa_regex.sh
#!/bin/bash

if [ $# -ne 1 ]
then
  echo "usage: $0 <sympa-domain>"
  exit 1
fi

DOMAIN=$1
SYMPA1=/etc/sympa/aliases.sympa.postfix
SYMPA2=/var/lib/sympa/sympa_aliases
REGEX=/etc/postfix-inbound/symparcptcheck.regexp
TMP=/tmp/symparcptcheck.regexp

# 2分以内にメーリングリストの更新があったらpostfixに反映する
LIMIT=120

if [ $(($(stat --format=%Y ${SYMPA1}) + ${LIMIT})) -ge $(date +%s) ] || [ $(($(stat --format=%Y ${SYMPA2}) + ${LIMIT})) -ge $(date +%s) ]
then
  /usr/bin/cp /dev/null ${TMP}
  for x in $(awk -F: '!/^#/{print $1}' ${SYMPA1} ${SYMPA2})
  do
      echo "/^${x}@${DOMAIN}/ OK" >> ${TMP}
  done
  /usr/bin/mv -f ${TMP} ${REGEX}
  systemctl reload postfix
fi

exit 0
_EOL_

chmod 755 /usr/local/bin/create_sympa_regex.sh

#- alias 関連ファイルのパーミション設定やデータ更新
chown sympa /etc/sympa/aliases.sympa.postfix.db
chown sympa /var/lib/sympa
postalias /var/lib/sympa/sympa_aliases

sed -i -e "s/\S*@my.domain.org/admin@${DOMAIN}/" -e "s/postmaster/admin@${DOMAIN}/" /etc/sympa/aliases.sympa.postfix
postalias /etc/sympa/aliases.sympa.postfix

#- postfix 用の宛先存在確認ルールを作成(上記コマンド実行後、2分以内に実行すること)
/usr/local/bin/create_sympa_regex.sh ${MLDOMAIN}

#- sympaでメーリングリストが追加された場合にpostfixに設定を反映するためのcron
echo "* * * * * root /usr/local/bin/create_sympa_regex.sh ${MLDOMAIN} >/dev/null 2>&1" >> /etc/cron.d/${DOMAIN}-cron

#-- nginx に設定追加
rm -f /etc/nginx/conf.d/sympa.conf
cat <<'_EOL_'> /etc/nginx/conf.d/https.d/sympa.conf
    location /sympa {
        include       /etc/nginx/fastcgi_params;
        fastcgi_pass  unix:/var/run/sympa/wwsympa.socket;

        # If you changed wwsympa_url in sympa.conf, change this regex too!
        fastcgi_split_path_info ^(/sympa)(.*)$;
        fastcgi_param SCRIPT_FILENAME /usr/libexec/sympa/wwsympa.fcgi;
        fastcgi_param PATH_INFO $fastcgi_path_info;

    }

    location /static-sympa/css {
        alias /var/lib/sympa/css;
    }
    location /static-sympa/pictures {
        alias /var/lib/sympa/pictures;
    }
    location /static-sympa {
        alias /usr/share/sympa/static_content;
    }
_EOL_

systemctl restart nginx

#-- sympa 起動
systemctl enable sympa wwsympa
systemctl start sympa wwsympa

#-- postfix の設定
#- メールを受け付ける対象にml用ドメインを追加
cat <<_EOF_>> /etc/postfix-inbound/relay_domains
${MLDOMAIN}
_EOF_

#- ml用ドメインはローカルに配送する(alias を適用する)設定を追加
cat <<_EOF_>> /etc/postfix-inbound/transport
${MLDOMAIN} local:
_EOF_

postmap /etc/postfix-inbound/transport

#- alias を sympa のファイルを参照するように変更、宛先チェックなどの設定も変更
postconf -c /etc/postfix-inbound -e alias_maps=hash:/etc/sympa/aliases.sympa.postfix,hash:/var/lib/sympa/sympa_aliases
postconf -c /etc/postfix-inbound -e alias_database=/etc/sympa/aliases.sympa.postfix,hash:/var/lib/sympa/sympa_aliases
postconf -c /etc/postfix-inbound -e smtpd_recipient_restrictions="check_recipient_access ldap:/etc/postfix-inbound/ldaprcptcheck.cf check_recipient_access regexp:/etc/postfix-inbound/symparcptcheck.regexp reject"
postconf -c /etc/postfix-inbound -e transport_maps="ldap:/etc/postfix-inbound/ldaptransport.cf hash:/etc/postfix-inbound/transport"

systemctl restart postfix

#-- rspamd の設定

#- envelope from が ml用ドメインの場合、SPAM判定のScoreを10下げる(これを実施しないと送信時にX-Spam: Yes が追加される)
cat <<_EOF_> /etc/rspamd/local.d/multimap.conf
WHITELIST_SENDER_DOMAIN {
  type = "from";
  filter = "email:domain";
  map = "/etc/rspamd/local.d/whitelist_sender_domain.map";
  score = -10.0
}
_EOF_

cat <<_EOF_>/etc/rspamd/local.d/whitelist_sender_domain.map
${MLDOMAIN}
_EOF_

#- DKIM と ARC の署名設定を追加する(詳しくはrspamdの構築設定手順を参照)
rspamadm dkim_keygen -d ${MLDOMAIN} -s default -b 2048
#-- 秘密鍵を記述
vi /etc/rspamd/local.d/keys/default.${MLDOMAIN}.key
chmod 600 /etc/rspamd/local.d/keys/default.${MLDOMAIN}.key
chown _rspamd. /etc/rspamd/local.d/keys/default.${MLDOMAIN}.key

#- ml用ドメインの設定を追加する
vi /etc/rspamd/local.d/dkim_signing.conf
vi /etc/rspamd/local.d/arc.conf

#- dkim署名するヘッダーを下記に変更する
vi /etc/rspamd/local.d/dkim_signing.conf
sign_headers = '(o)from:(o)date:(o)message-id:(o)to:(o)cc:(o)mime-version:(o)content-type:(o)content-transfer-encoding:resent-to:resent-cc:resent-from:resent-sender:resent-message-id:(o)in-reply-to:(o)references:list-id:list-owner:list-unsubscribe:list-subscribe:list-post';

#- arc署名するヘッダーを下記に変更する
vi /etc/rspamd/local.d/arc.conf
sign_headers = "(o)from:(o)sender:(o)reply-to:(o)date:(o)message-id:(o)to:(o)cc:(o)mime-version:(o)content-type:(o)content-transfer-encoding:resent-to:resent-cc:resent-from:resent-sender:resent-message-id:(o)in-reply-to:(o)references:list-id:list-owner:list-unsubscribe:list-subscribe:list-post:dkim-signature";

#- rspamd 再起動
systemctl restart rspamd

#-- nsd の設定
IPV4=$(ip addr show eth0 | awk '/inet /{print $2}' | awk -F\/ '{print $1}')
IPV6=$(ip addr show eth0 | awk '/inet6 /&&/global/{print $2}' | awk -F\/ '{print $1}')

cat <<_EOL_>>/etc/nsd/zone/${DOMAIN}.zone
ml                      IN      MX 100  ${DOMAIN}.
ml                      IN      TXT     "v=spf1 +ip4:${IPV4} +ip6:${IPV6} -all"
_dmarc.ml               IN      TXT     "v=DMARC1; p=none; rua=mailto:root@${DOMAIN}"
_adsp._domainkey.ml     IN      TXT     "dkim=discardable"
default._domainkey.ml   IN      TXT     ( "v=DKIM1; k=rsa; "
                                        "p=共通鍵を記述"
                                        ) ;
_EOL_

systemctl restart nsd

・初期パスワードの作成

  • https://masdon.life/sympa/firstpasswd で 管理者ユーザのパスワードを作成する

26. Realtime Monitoring – netdata [さくらのVPS/CentOS7]

・ netdata のインストール

#-- 必要なパッケージのインストール
yum install autoconf automake libmnl-devel libuuid-devel libuv-devel lm-sensors MySQL-python nc pkgconfig python-psycopg2 PyYAML

#-- netdata のインストールと起動
cd ~/work/src/
git clone https://github.com/firehol/netdata.git --depth=1
cd netdata/
./netdata-installer.sh --install /opt

systemctl enable netdata
systemctl start netdata

#-- netdata用の nginx の設定追加
cat <<'_EOL_'> /etc/nginx/conf.d/https.d/netdata.conf
  location ^~ /netdata {
    location /netdata/ {
      proxy_pass       http://127.0.0.1:19999/;
      proxy_set_header Host      $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      auth_ldap "Basic Auth Location";
      auth_ldap_servers ldap1;
    }
  }
_EOL_

systemctl restart nginx