Google smtp-relay サービスを使って、SMTP relay しても gmail.com から迷惑メールに判定されない方法を考える

# What is this ?
最近、gmail.com で迷惑メール対策として、DMARC および SPF の義務化等の対応に追われています。
クラウドのメールサービスをそのまま使用してるドメインはいいのですが、問題なのが今まで何も考えずに MTA へメールデータを送るだけの ( ≒ 認証なしにメールを送信していた ) システムが、なりすまし対策を講じると結構なコストになる点です。

そこで、Google のサービスを利用している弊社で、もうちょっといい感じにコストを下げられないか、というのがこの記事の目的です。

最終的には色々とケースを考えて、どのように利用するかを判断すべきですが、今回は Case 1 のみ取り上げます。

# Google の SMTP Relay Service

この辺に情報があります。

https://support.google.com/a/answer/176600?hl=ja

https://support.google.com/a/answer/2956491?hl=ja

思えば、弊社にとって上記のサービスは「まさに空気」だったのですが、ここへ来て「えらいひと」からの注目度が俄然 UP しました。

上記手順によれば Google Vault の設定が必須ですが、となりの人がカスタマーサポートに chat して聞いたら「Gmail のアカウントに送信データが積もり、会社のストレージを圧迫するので Google Vault はやめておけ」的な話をしていたそうで。
Google Vault はどちらかというとコンプライアンス管理目的のサービスだと思っていたので、Gmail にデータが残りそう ? な点は確認する必要がありそうです。下記 URL が Google Vault のざっくりとした説明。

https://support.google.com/vault/answer/2462365?hl=ja

私の検証環境では「無効なリクエストです」と表示されたので今回は Google Vault についての検証はしていません。

# 検証環境

:::note info
検証は実ドメインを使用しており、それを Qiita に転記する際に `example.com` 等に置換しています。
「なにか見た」場合には、騒ぎ立てずにこっそり教えてもらえると助かります。
:::

## 共通事項

組織で保有するドメインを `example.com` とします。

## Case 1. SMTP Client が直接 Google SMTP Relay にアクセスする

![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154569/fb4bdd4b-d26d-2ebe-1d37-ea16eed1d792.png)

ここでは
- `example.com` は Google Workspace を契約して Gmail で読み書きできる
- 送信するメールアドレス : `foo@example.com`
- `foo@example.com` は Google Workspace 上にある有効なアカウント
- 受信するアドレス : `var@example.com` および `hoge@gmail.com`
- `foo@example.com` から `hoge@gmail.com` は転送なし ( 直接送信 )

とします。

### client ( sendmail ) の構築 ( ひな形 )

Windows 11 に VirtualBox をインストール、Vagrant を使用して Ubuntu 20.04 ( focal ) 上に sendmail でメールを送信する環境を構築します。

#### ひな形構築に関するデザインノート

- sendmail を対象にする
どうみても sendmail は一般的に実環境で利用されているとは考えづらいのですが、例えば「sendmail で～したことを postfix ではどうするか」といったノウハウはあると思えるので汎用性を考えて sendmail を選択
- メール中継は SMART_HOST を使用する
google のドキュメントにそう書いてあったから。
また、nullclient で MASQUERADE をうまく扱えなかったから
- vagrant で構築する
dig などで確認する際に ssh login できると便利
- VirtualBox を使用する
vagrant 標準だから
- github には公開しない
そのまんま使ってもらうのが目的ではなく、あくまでも参考としてもらうため
- AS IS で出す
めんどくさくなった ( 現時点 )

#### ひな形の詳細

ディレクトリ構造は以下の通り
```
-- : root
 |- data/ # 必要に応じて vagnrantup.ps1 が作成
 |- vagrantup.ps1
 |- Vagrantfile
 |- bootstrap.sh
```

<details><summary>vagrantup.ps1</summary>

```powershell
#
# vagrantup.ps1
#
$datadir = "data"
if ( -Not (Test-Path $datadir)){
  New-Item -ItemType Directory -Path $datadir
}

vagrant destroy --force

$Env:VAGRANT_DEFAULT_PROVIDER = "virtualbox"

vagrant plugin install vagrant-reload
vagrant plugin uninstall vagrant-vbguest

vagrant up

vagrant ssh-config > ssh.config

```
</details>

<details><summary>Vagrantfile</summary>

```ruby
# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
  # The most common configuration options are documented and commented below.
  # For a complete reference, please see the online documentation at
  # https://docs.vagrantup.com.

  # Every Vagrant development environment requires a box. You can search for
  # boxes at https://vagrantcloud.com/search.
  config.vm.box = "ubuntu/focal64"
  config.vm.box_version = "20240118.0.0"
  config.vm.boot_timeout = 1500

  # Disable automatic box update checking. If you disable this, then
  # boxes will only be checked for updates when the user runs
  # `vagrant box outdated`. This is not recommended.
  # config.vm.box_check_update = false

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine. In the example below,
  # accessing "localhost:8080" will access port 80 on the guest machine.
  # NOTE: This will enable public access to the opened port
  # config.vm.network "forwarded_port", guest: 80, host: 8080

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine and only allow access
  # via 127.0.0.1 to disable public access
  # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  # config.vm.network "private_network", ip: "192.168.33.10"

  # Create a public network, which generally matched to bridged network.
  # Bridged networks make the machine appear as another physical device on
  # your network.
  # config.vm.network "public_network"

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  # config.vm.synced_folder "../data", "/vagrant_data"
  config.vm.synced_folder "data", "/vagrant_data"

  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  # config.vm.provider "virtualbox" do |vb|
  #   # Display the VirtualBox GUI when booting the machine
  #   vb.gui = true
  #
  #   # Customize the amount of memory on the VM:
  #   vb.memory = "1024"
  # end
  #
  # View the documentation for the provider you are using for more
  # information on available options.
  config.vm.provider "virtualbox" do |vb|
    vb.check_guest_additions = false
    vb.gui = false
    vb.name = "smtp-relay-client-tmpl"
    vb.cpus = 2
    vb.memory = 8192
    vb.customize [ "modifyvm", :id, "--uartmode1", "disconnected" ]
    vb.customize [ "modifyvm", :id, "--graphicscontroller", "vmsvga" ]
    vb.customize [ "modifyvm", :id, "--ioapic", "on"]
    vb.customize [ "modifyvm", :id, "--accelerate3d", "off"]
  end
  config.vm.hostname = "smtp-relay-client"

  # Enable provisioning with a shell script. Additional provisioners such as
  # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the
  # documentation for more information about their specific syntax and use.
  # config.vm.provision "shell", inline: <<-SHELL
  #   apt-get update
  #   apt-get install -y apache2
  # SHELL
  config.vm.provision "shell", path: "bootstrap.sh", args: ["foo@example.com", "example.com.", "-v"], privileged: true
  #config.trigger.after [:provision] do |t|
  #  t.name = "Reboot after provisioning"
  #  t.run = { :inline => "vagrant reload" }
  #end
end
```
</details>
<details><summary>bootstrap.sh</summary>

```bash
#!/bin/bash
#
# bootstrap.sh ( for mail client : MTA ver )
# usage : bootstrap.sh <test_mail_addr> [<domain>] [<mta host name>] [<sendmail options>]
#    domain : default is "example.com."
#    mta host name : default is "smtp-relay.gmail.com."
#    sendmail options : default is ""-bm -d -v"
#
set -x

##
## prepare
##

#
# check variables from parameter
#
test_mail_addr=$1
domain_name=${2:-"example.com."}
mta_host=${3:-"smtp-relay.gmail.com."}
sendmailopts=${4:-"-bm -d -v"}

#
# get today date
#
today=$(date +\%Y%m%d)

#
# check whether DNS name is FQDN
#
is_dotted=$(echo "${domain_name}" | grep "\.$")
if [ -z "${is_dotted}" ] ; then
  domain_name="${domain_name}."
fi

is_dotted=$(echo "${mta_host}" | grep "\.$")
if [ -z "${is_dotted}" ] ; then
  mta_host="${mta_host}."
fi

#
# update and install software
#
sudo apt-get update
sudo apt-get -yq upgrade

sudo apt-get install -yq sendmail expect jq

#
# modify /etc/hosts
#
local_short=$(hostname)
local_ip=$(ip -detail -json address list \
	| jq -r '.[] | select(.addr_info[].scope=="global") | .addr_info[].local' \
	| grep "^[0-9]" \
	| head -1)

local_fqdn="${local_short}.${domain_name}"

tmphostsfile=$(mktemp /tmp/hosts.XXXXXX)
echo "127.0.0.1	${local_fqdn}	$(hostname)" > ${tmphostsfile}
echo "${local_ip}	${local_fqdn}" >> ${tmphostsfile}
cat /etc/hosts >> ${tmphostsfile}
sudo mv /etc/hosts /etc/hosts.${today}
sudo mv ${tmphostsfile} /etc/hosts
# for debug
cat /etc/hosts

# for test
getent hosts ${local_ip}
ping -4 -c 2 ${local_ip}
ping -4 -c 2 ${local_fqdn}

##
## configure sendmail setting files
##

#
# prepare  
#
basedir="/etc/mail"

#
# make /etc/mail/local-host-names
#
local_host_name="${basedir}/local-host-names"
echo "${domain_name}"        | sudo tee    ${local_host_name}
echo "${local_fqdn}"         | sudo tee -a ${local_host_name}
echo "${local_short}."        | sudo tee -a ${local_host_name}
echo "localhost."             | sudo tee -a ${local_host_name}
echo "localhost.localdomain." | sudo tee -a ${local_host_name}
# for debug
cat ${local_host_name}

#
# make /etc/mail/masquerade-domains
#
masquerade_domains="${basedir}/masquerade-domains"
echo "${mta_host}." | sudo tee -a ${masquerade_domains}
cat ${local_host_name} | sudo tee ${masquerade_domains}
# for debug
cat ${masquerade_domains}


#
# make relay-domains file
#
relay_domains="${basedir}/relay-domains"
echo "${domain_name}" | sudo tee -a ${relay_domains}
# for debug
cat ${relay_domains}

#
# make trusted-users file
#
trusted_user_file="${basedir}/trusted-users"
if [ ! -e ${trusted_user_file} ] ; then
  touch ${trusted_user_file}
fi

has_root=$(grep ^root\$ ${trusted_user_file})
if [ -z "${has_root}" ] ; then
  echo "root" | sudo tee -a ${trusted_user_file}
fi
has_smmta=$(grep ^smmta\$ ${trusted_user_file})
if [ -z "${has_smmta}" ] ; then
  echo "smmta" | sudo tee -a ${trusted_user_file}
fi
has_smmsp=$(grep ^smmsp\$ ${trusted_user_file})
if [ -z "${has_smmsp}" ] ; then
  echo "smmsp" | sudo tee -a ${trusted_user_file}
fi
# for debug
cat ${trusted_user_file}

#
# precheck
#
if [ -z "${test_mail_addr}" ] ; then
  echo "test mail address not found."
  exit 1
fi

##
## show default settings about sendmail.mc and submit.mc ( if you need them )
##
#echo -n "This is a test for sendmail.mc." | sudo -u smmta -g smmsp /usr/lib/sendmail -Am ${sendmailopts} ${test_mail_addr}
#cat /etc/mail/sendmail.mc
#echo -n "This is a test for submit.mc."   | sudo -u smmsp -g smmsp /usr/lib/sendmail -Ac ${sendmailopts} ${test_mail_addr}
#cat /etc/mail/submit.mc

##
## configure sendmail.mc
##
original_sendmail_mc="${basedir}/sendmail.mc"
backup_sendmail_mc="${basedir}/sendmail.mc.${today}"

#
# modify sendmail.mc
#
sudo mv ${original_sendmail_mc} ${backup_sendmail_mc}
trap "if [ ! -e ${original_sendmail_mc} ] ; then ; mv ${backup_sendmail_mc} ${original_sendmail_mc}" EXIT

cat <<'EOF' > ${original_sendmail_mc}
divert(-1)dnl
#-----------------------------------------------------------------------------
# $Sendmail: debproto.mc,v 8.15.2 2020-03-08 00:39:49 cowboy Exp $
#
# Copyright (c) 1998-2010 Richard Nelson.  All Rights Reserved.
#
# cf/debian/sendmail.mc.  Generated from sendmail.mc.in by configure.
#
# sendmail.mc prototype config file for building Sendmail 8.15.2
#
# Note: the .in file supports 8.7.6 - 9.0.0, but the generated
#       file is customized to the version noted above.
#
# This file is used to configure Sendmail for use with Debian systems.
#
# If you modify this file, you will have to regenerate /etc/mail/sendmail.cf
# by running this file through the m4 preprocessor via one of the following:
#       * make   (or make -C /etc/mail)
#       * sendmailconfig
#       * m4 /etc/mail/sendmail.mc > /etc/mail/sendmail.cf
# The first two options are preferred as they will also update other files
# that depend upon the contents of this file.
#
# The best documentation for this .mc file is:
# /usr/share/doc/sendmail-doc/cf.README.gz
#
#-----------------------------------------------------------------------------
divert(0)dnl
#
#   Copyright (c) 1998-2005 Richard Nelson.  All Rights Reserved.
#
#  This file is used to configure Sendmail for use with Debian systems.
#
define(`_USE_ETC_MAIL_')dnl
include(`/usr/share/sendmail/cf/m4/cf.m4')dnl
include(`/etc/mail/tls/starttls.m4')dnl
VERSIONID(`$Id: sendmail.mc, v 8.15.2-18 2020-03-08 00:39:49 cowboy Exp ')
OSTYPE(`debian')dnl
DOMAIN(`debian-mta')dnl
dnl # Items controlled by /etc/mail/sendmail.conf - DO NOT TOUCH HERE
undefine(`confHOST_STATUS_DIRECTORY')dnl        #DAEMON_HOSTSTATS=
dnl # Items controlled by /etc/mail/sendmail.conf - DO NOT TOUCH HERE
dnl #
dnl # General defines
dnl #
dnl # SAFE_FILE_ENV: [undefined] If set, sendmail will do a chroot()
dnl #   into this directory before writing files.
dnl #   If *all* your user accounts are under /home then use that
dnl #   instead - it will prevent any writes outside of /home !
dnl #   define(`confSAFE_FILE_ENV',             `')dnl
dnl #
dnl # Daemon options - restrict to servicing LOCALHOST ONLY !!!
dnl # Remove `, Addr=' clauses to receive from any interface
dnl # If you want to support IPv6, switch the commented/uncommentd lines
dnl #
define(`confDOMAIN_NAME', `${domain_name}')dnl
define(`confRUN_AS_USER', `smmta:smmsp')dnl
define(`LOCAL_USER', `root daemon smmsp smmta postmaster mail')
define(`SMART_HOST', `${mta_host}')dnl

MASQUERADE_AS(`${domain_name}')
MASQUERADE_DOMAIN_FILE(`${masquerade_domains}')

FEATURE(`use_cw_file')dnl
FEATURE(`no_default_msa')dnl
FEATURE(`always_add_domain')
FEATURE(`masquerade_entire_domain')
FEATURE(`masquerade_envelope')
FEATURE(`allmasquerade')
FEATURE(`limited_masquerade')
FEATURE(`access_db', `hash -T<TMPF> -o /etc/mail/access')

DAEMON_OPTIONS(`Family=inet,  Name=MTA-v4, Port=smtp, Addr=127.0.0.1')dnl
DAEMON_OPTIONS(`Family=inet,  Name=MSP-v4, Port=submission, M=Ea, Addr=127.0.0.1')dnl

MAILER(`local')
MAILER(`smtp')
EOF

sed -i -e "s#\${domain_name}#${domain_name}#"               ${original_sendmail_mc}
sed -i -e "s#\${mta_host}#${mta_host}#"                     ${original_sendmail_mc}
sed -i -e "s#\${mta_port}#${mta_port}#"                     ${original_sendmail_mc}
sed -i -e "s#\${local_fqdn}#${local_fqdn}#"                 ${original_sendmail_mc}
sed -i -e "s#\${masquerade_domains}#${masquerade_domains}#" ${original_sendmail_mc}
sed -i -e "s#\${local_short}#${local_short}#"               ${original_sendmail_mc}

# for debug
sudo cat ${original_sendmail_mc}

##
## configure submit.mc
##
original_submit_mc="${basedir}/submit.mc"
backup_submit_mc="${basedir}/submit.mc.${today}"

sudo mv ${original_submit_mc} ${backup_submit_mc}
trap "if [ ! -e ${original_submit_mc} ] ; then ; mv ${backup_submit_mc} ${original_submit_mc}" EXIT

cat <<'EOF' > ${original_submit_mc}
divert(-1)dnl
#-----------------------------------------------------------------------------
# $Sendmail: submit.mc,v 8.15.2 2020-03-08 00:39:49 cowboy Exp $
#
# Copyright (c) 2000-2010 Richard Nelson.  All Rights Reserved.
#
# cf/debian/submit.mc.  Generated from submit.mc.in by configure.
#
# submit.mc prototype config file for building Sendmail 8.15.2
#
# Note: the .in file supports 8.7.6 - 9.0.0, but the generated
#       file is customized to the version noted above.
#
# This file is used to configure Sendmail for use with Debian systems.
#
# If you modify this file, you will have to regenerate /etc/mail/submit.cf
# by running this file through the m4 preprocessor via one of the following:
#       * make  (or make -C /etc/mail)
#       * sendmailconfig
#       * m4 /etc/mail/submit.mc > /etc/mail/submit.cf
# The first two options are preferred as they will also update other files
# that depend upon the contents of this file.
#
# The best documentation for this .mc file is:
# /usr/share/doc/sendmail-doc/cf.README.gz
#
#-----------------------------------------------------------------------------
divert(0)dnl
#
#   Copyright (c) 2000-2002 Richard Nelson.  All Rights Reserved.
#
#  This file is used to configure Sendmail for use with Debian systems.
#
define(`_USE_ETC_MAIL_')dnl
define(`confDOMAIN_NAME', `${local_fqdn}')dnl
define(`confRUN_AS_USER', `smmsp:smmsp')dnl
define(`LOCAL_USER', `root daemon smmsp smmta postmaster mail')dnl
include(`/usr/share/sendmail/cf/m4/cf.m4')dnl
include(`/etc/mail/tls/starttls.m4')dnl
VERSIONID(`$Id: submit.mc, v 8.15.2-18 2020-03-08 00:39:49 cowboy Exp $')
OSTYPE(`debian')dnl
DOMAIN(`debian-msp')dnl
dnl #
dnl #---------------------------------------------------------------------
dnl # Masquerading information, if needed, should go here
dnl # You likely will not need this, as the MTA will do it
dnl #---------------------------------------------------------------------
dnl MASQUERADE_AS()dnl
dnl FEATURE(`masquerade_envelope')dnl
dnl #
dnl #---------------------------------------------------------------------
dnl # The real reason we're here: the FEATURE(msp)
dnl # NOTE WELL:  MSA (587) should have M=Ea, so we need to use stock 25
dnl #---------------------------------------------------------------------
MASQUERADE_DOMAIN_FILE(`${masquerade_domains}'))
MASQUERADE_AS(`${domain_name}')
FEATURE(`use_cw_file')dnl
FEATURE(`masquerade_envelope')dnl
FEATURE(`always_add_domain')
FEATURE(`masquerade_entire_domain')
FEATURE(`allmasquerade')
FEATURE(`limited_masquerade')
FEATURE(`msp', `[127.0.0.1]', `25')dnl
dnl #
dnl #---------------------------------------------------------------------
dnl # Some minor cleanup from FEATURE(msp)
dnl #---------------------------------------------------------------------
dnl #
dnl #---------------------------------------------------------------------
EOF

sed -i -e "s#\${domain_name}#${domain_name}#"               ${original_submit_mc}
sed -i -e "s#\${mta_host}#${mta_host}#"                     ${original_submit_mc}
sed -i -e "s#\${mta_port}#${mta_port}#"                     ${original_submit_mc}
sed -i -e "s#\${local_fqdn}#${local_fqdn}#"                 ${original_submit_mc}
sed -i -e "s#\${local_short}#${local_short}#"               ${original_submit_mc}
sed -i -e "s#\${masquerade_domains}#${masquerade_domains}#" ${original_submit_mc}

# for debug
sudo cat ${original_submit_mc}


# run "sudo sendmailconfig"
# if you config sendmail.mc and submit.mc, then run "sudo sendmailconfig"
sctmpfile=$(mktemp /tmp/XXXXXX.expect)
trap "rm -f ${sctmpfile}" EXIT
cat <<EOF > ${sctmpfile}
spawn sudo sendmailconfig
sleep 1
expect {"/sendmail.conf? [Y]"}
sleep 1
send "y\r"
expect {"sendmail.mc? [Y]"}
sleep 1
send "y\r"
expect {"new configuration? [Y]"}
sleep 1
send "y\r"
sleep 1
expect eof
EOF
sudo expect ${sctmpfile}

# enable sendmail daemon
sudo systemctl enable sendmail
sudo systemctl start sendmail

# make test_mail.sh
homedir=$(getent passwd $(whoami) | awk -F: '{print $6;}')
test_script=${homedir}/test_mail.sh

cat <<EOF > ${test_script}
#!/bin/bash
#
# test_mail.sh
#
# usage: test_mail.sh <mail addr> [<sendmail options>]
#

# command runner
runner=\$(whoami)
curtime=\$(TZ='Asia/Tokyo' date "+%Y/%m/%d %H:%M:%S")

#
# set command runner
# usage: set_runner ["-Am"|"-Ac"]
# "-Am" : use sendmail.cf
# "-Ac" : use submit.cf
# example: check_runner "-Am"
# default : "-Am"
#
function set_runner() {
  runner=\$(echo "\${runner}" | awk -F' ' '{print \$1;}')
  mode=\${1:-"-Am"}
  optlist=\$(sudo /usr/lib/sendmail \${mode} -v -d37.1 < /dev/null)
  queuedir=\$(echo -e "\${optlist}" | grep -i -e "QueueDirectory" | awk -F' ' '{print \$3;}' | sed -e "s#^.*\=##")
  queuegrp=\$(sudo ls -al -d --dereference \${queuedir}  | awk -F' ' '{print \$4;}')
  queueuser=\$(sudo ls -al -d --dereference \${queuedir} | awk -F' ' '{print \$3;}')
  has_group=\$(id -a "\${runner}" | grep -i \${queuegrp})
  if [ -z "\${has_group}" ] ; then
    runner="\${queueuser} -g \${queuegrp}"
  else
    runner="\${queueuser}"
  fi
}


#
# test for sendmail.cf
#
toaddr=\$1
sendmailopts=\${2:-"${sendmailopts}"}

msg="To: <\${toaddr}>
Subject: test ( by \${runner} at \${curtime} )

This is a test mail.
."

#
# test for sendmail.cf
#
set_runner "-Am"
echo -e "\${msg}" | sudo -u \${runner} /usr/lib/sendmail -Am \${sendmailopts} \${toaddr}
echo -e "exit status ( sendmail.cf ) : \$?"

#
# test for submit.cf
#
set_runner "-Ac"
echo -e "\${msg}" | sudo -u \${runner} /usr/lib/sendmail -Ac \${sendmailopts} \${toaddr}
echo -e "exit status ( submit.cf ) : \$?"
EOF

# for debug
sudo cat ${test_script}

# test test_mail.sh
sudo bash -x ${test_script} ${test_mail_addr}

```
</details>

### client ( sendmail ) の構築

上記ひな形をそのまま実行することで、何も設定していない状態での smtp-relay.gmail.com との通信を確認することができます。

この時点での sendmail.mc および submit.mc は以下の通り

:::note info
コメントは抜いています
:::

sendmail.mc

```bash
define(`_USE_ETC_MAIL_')dnl
include(`/usr/share/sendmail/cf/m4/cf.m4')dnl
include(`/etc/mail/tls/starttls.m4')dnl
VERSIONID(`$Id: sendmail.mc, v 8.15.2-18 2020-03-08 00:39:49 cowboy Exp ')
OSTYPE(`debian')dnl
DOMAIN(`debian-mta')dnl

undefine(`confHOST_STATUS_DIRECTORY')dnl

define(`confDOMAIN_NAME', `${domain_name}')dnl
define(`confRUN_AS_USER', `smmta:smmsp')dnl
define(`LOCAL_USER', `root daemon smmsp smmta postmaster mail')
define(`SMART_HOST', `smtp-relay.gmail.com.')dnl

MASQUERADE_AS(`${domain_name}')
MASQUERADE_DOMAIN_FILE(`/etc/mail/masquerade-domains')

FEATURE(`use_cw_file')dnl
FEATURE(`no_default_msa')dnl
FEATURE(`always_add_domain')
FEATURE(`masquerade_entire_domain')
FEATURE(`masquerade_envelope')
FEATURE(`allmasquerade')
FEATURE(`limited_masquerade')
FEATURE(`access_db', `hash -T<TMPF> -o /etc/mail/access')

DAEMON_OPTIONS(`Family=inet,  Name=MTA-v4, Port=smtp, Addr=127.0.0.1')dnl
DAEMON_OPTIONS(`Family=inet,  Name=MSP-v4, Port=submission, M=Ea, Addr=127.0.0.1')dnl

MAILER(`local')
MAILER(`smtp')
```
submit.mc

```bash
define(`_USE_ETC_MAIL_')dnl
define(`confDOMAIN_NAME', `${local_fqdn}')dnl
define(`confRUN_AS_USER', `smmsp:smmsp')dnl
define(`LOCAL_USER', `root daemon smmsp smmta postmaster mail')dnl
include(`/usr/share/sendmail/cf/m4/cf.m4')dnl
include(`/etc/mail/tls/starttls.m4')dnl
VERSIONID(`$Id: submit.mc, v 8.15.2-18 2020-03-08 00:39:49 cowboy Exp $')
OSTYPE(`debian')dnl
DOMAIN(`debian-msp')dnl

MASQUERADE_DOMAIN_FILE(`/etc/mail/masquerade-domains'))
MASQUERADE_AS(`${domain_name}')
FEATURE(`use_cw_file')dnl
FEATURE(`masquerade_envelope')dnl
FEATURE(`always_add_domain')
FEATURE(`masquerade_entire_domain')
FEATURE(`allmasquerade')
FEATURE(`limited_masquerade')
FEATURE(`msp', `[127.0.0.1]', `25')dnl
```

:::note warn
実行する前に以下の点を確認してください
- `example.com` の箇所は所有している実ドメインを指定してください
- `foo@example.com` の箇所は所有しているメールアドレスを指定してください
- `bootstrap.sh` および `test_mail.sh` のデフォルトでの sendmail のオプションは `sendmail -d` を実行することに注意してください。つまり、大量の sendmail のデバッグ情報が流れます。通常は bootstrap.sh のオプションとして "-v" を指定するとよいでしょう
:::

- 実行結果 ( `bootstrap.sh` を実行することで作成される `/root/test_mail.sh` を `bash /root/test_mail.sh var@example.com "-v"` として実行した場合に、smtp-relay.gmail.com とのやり取りのみをコピー )

sendmai.mc の場合

```bash
foo@example.com... Connecting to smtp-relay.gmail.com. via relay...
220 smtp-relay.gmail.com ESMTP e19-20020a056122131300b004b74b27053dsm617387vkp.6 - gsmtp
>>> EHLO example.com.
250-smtp-relay.gmail.com at your service, [XXX.XXX.XXX.XXX]
250-SIZE 157286400
250-8BITMIME
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8
>>> STARTTLS
220 2.0.0 Ready to start TLS
>>> EHLO example.com.
250-smtp-relay.gmail.com at your service, [XXX.XXX.XXX.XXX]
250-SIZE 157286400
250-8BITMIME
250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8
>>> MAIL From:<smmta@example.com> SIZE=95 AUTH=smmta@example.com.
550-5.7.1 Invalid credentials for relay [XXX.XXX.XXX.XXX]. The IP address you've
550-5.7.1 registered in your Workspace SMTP Relay service doesn't match the
550-5.7.1 domain of the account this email is being sent from. If you are
550-5.7.1 trying to relay mail from a domain that isn't registered under your
550-5.7.1 Workspace account or has empty envelope-from, you must configure your
550-5.7.1 mail server either to use SMTP AUTH to identify the sending domain or
550-5.7.1 to present one of your domain names in the HELO or EHLO command. For
550-5.7.1 more information, go to
550 5.7.1  https://support.google.com/a/answer/6140680#invalidcred e19-20020a056122131300b004b74b27053dsm617387vkp.6 - gsmtp
/var/lib/sendmail/dead.letter... Saved message in /var/lib/sendmail/dead.letter
Closing connection to smtp-relay.gmail.com.
>>> QUIT
```

submit.mc の場合

```bash
foo@example.com... Connecting to [127.0.0.1] via relay...
220 example.com. ESMTP Sendmail 8.15.2/8.15.2/Debian-18; Wed, 21 Feb 2024 07:43:12 GMT; (No UCE/UBE) logging access from: [127.0.0.1](FORGED)-smtp-relay-client.example.com. [127.0.0.1] (may be forged)
>>> EHLO smtp-relay-client.example.com.
250-example.com. Hello smtp-relay-client.example.com. [127.0.0.1] (may be forged), pleased to meet you
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-EXPN
250-VERB
250-8BITMIME
250-SIZE
250-DSN
250-ETRN
250-AUTH DIGEST-MD5 CRAM-MD5
250-STARTTLS
250-DELIVERBY
250 HELP
>>> VERB
250 2.0.0 Verbose mode
>>> STARTTLS
220 2.0.0 Ready to start TLS
>>> EHLO smtp-relay-client.example.com.
250-example.com. Hello smtp-relay-client.example.com. [127.0.0.1] (may be forged), pleased to meet you
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-EXPN
250-VERB
250-8BITMIME
250-SIZE
250-DSN
250-ETRN
250-AUTH DIGEST-MD5 CRAM-MD5
250-DELIVERBY
250 HELP
>>> VERB
250 2.0.0 Verbose mode
>>> MAIL From:<smmsp@example.com> SIZE=95 AUTH=smmsp@smtp-relay-client.example.com.
250 2.1.0 <smmsp@example.com>... Sender ok
>>> RCPT To:<foo@example.com>
>>> DATA
250 2.1.5 <foo@example.com>... Recipient ok
354 Enter mail, end with "." on a line by itself
>>> .
050 <foo@example.com>... Connecting to smtp-relay.gmail.com. via relay...
050 220 smtp-relay.gmail.com ESMTP jp7-20020ad45f87000000b0068fa300e9c8sm32215qvb.48 - gsmtp
050 >>> EHLO example.com.
050 250-smtp-relay.gmail.com at your service, [XXX.XXX.XXX.XXX]
050 250-SIZE 157286400
050 250-8BITMIME
050 250-STARTTLS
050 250-ENHANCEDSTATUSCODES
050 250-PIPELINING
050 250-CHUNKING
050 250 SMTPUTF8
050 >>> STARTTLS
050 220 2.0.0 Ready to start TLS
050 >>> EHLO example.com.
050 250-smtp-relay.gmail.com at your service, [XXX.XXX.XXX.XXX]
050 250-SIZE 157286400
050 250-8BITMIME
050 250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH
050 250-ENHANCEDSTATUSCODES
050 250-PIPELINING
050 250-CHUNKING
050 250 SMTPUTF8
050 >>> MAIL From:<smmsp@example.com> SIZE=414 AUTH=<>
050 550-5.7.1 Invalid credentials for relay [XXX.XXX.XXX.XXX]. The IP address you've
050 550-5.7.1 registered in your Workspace SMTP Relay service doesn't match the
050 550-5.7.1 domain of the account this email is being sent from. If you are
050 550-5.7.1 trying to relay mail from a domain that isn't registered under your
050 550-5.7.1 Workspace account or has empty envelope-from, you must configure your
050 550-5.7.1 mail server either to use SMTP AUTH to identify the sending domain or
050 550-5.7.1 to present one of your domain names in the HELO or EHLO command. For
050 550-5.7.1 more information, go to
050 550 5.7.1  https://support.google.com/a/answer/6140680#invalidcred jp7-20020ad45f87000000b0068fa300e9c8sm32215qvb.48 - gsmtp
050 <smmsp@example.com>... Using cached ESMTP connection to smtp-relay.gmail.com. via relay...
050 >>> RSET
050 <smmsp@example.com>... Deferred: Connection reset by smtp-relay.gmail.com.
250 2.0.0 41L7hCSd013473 Message accepted for delivery
foo@example.com... Sent (41L7hCSd013473 Message accepted for delivery)
Closing connection to [127.0.0.1]
>>> QUIT
221 2.0.0 example.com. closing connection
```

エラー文中に登場する URL はいずれも同じもの ( 下記サイト ) を指しています。

https://support.google.com/a/answer/6140680#invalidcred

### Google の設定を変更し、smtp-relay.gmail.com を使用するように修正する

手順は以下の URL を参考に ( 再掲 )

https://support.google.com/a/answer/2956491?sjid=4887586142533983799-AP#1_config_notes&2_comprehensive_storage&3_sendinglimitsforrelay&access_team_data

0\. 事前準備

アクセス元の IP アドレスを確認する。
client ( sendmail ) の構築の際に `smtp-relay.gmail.com` からのレスポンスに IP アドレスが表示されているのでそれを控えておく。
下記の例では XXX.XXX.XXX.XXX がアクセス元の IP アドレスになる。

`例 :  050 250-smtp-relay.gmail.com at your service, [XXX.XXX.XXX.XXX]` 

1\. Google の管理コンソールに Workspace の管理者アカウントでログインする

URL は以下の通り

https://admin.google.com/

2\. 管理コンソールから「アプリ」→ 「Google Workspace」→「Gmail」→「コンプライアンス」にアクセス

![004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154569/a059ed88-17cb-b8d5-8eff-2c82e307bf60.png)

3\. 「包括的なメールストレージ」の鉛筆アイコンをクリック

![005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154569/6aade5c3-a58b-873b-27b5-4a7f2717872f.png)

4\. 「関連付けられているユーザーのメールボックスに、すべての送受信メールのコピーを保存します。」にチェックを入れた後に、「オーバーライド」をクリック

![006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154569/7710a7ec-058b-de38-46f5-5daeeb541922.png)

5\. 管理コンソールのトップページに戻り、「アプリ」→「Google Workspace」→「Gmail」→「ルーティング」にアクセスする。

![007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154569/5e576293-c52b-3673-6809-a6ea19469b13.png)

6\. 新規に SMTP リレーサービスを追加する

「別のルールを追加」をクリック。

![008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154569/0a0a98b2-392c-8855-4da5-aa6f3a23ab08.png)

ポップアップしたダイアログに以下の内容を記入 ( 1 )

- 設定名
今回は「Google サーバー経由の SMTP リレー用」とした
- 許可する送信者
今回は「ドメイン内のアドレスのみ」とした
- 認証
「指定したアドレスからのみメールを受信する」を選択

![009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154569/e2e33d9e-fed1-c53b-80e4-8637ad2c27ee.png)

ポップアップしたダイアログに以下の内容を記入 ( 2 )

- IP アドレスまたは IP 範囲

「追加」をクリック

![010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154569/a3207407-3d16-f73b-2249-4d9e0dc322ae.png)

表示されたダイアログに以下の内容を記入

![011.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154569/6d272a94-4cc4-752a-283f-d178bb4b50c3.png)

- - 説明
今回は「Test for example.com」とした ( 実際には example.com の箇所は実ドメインを記載 )
- - IP アドレスまたは IP アドレス範囲を入力
手順 0 で控えた IP アドレス ( XXX.XXX.XXX.XXX ) を入力 ( 実際には使用している Global IP を入力 )

入力後「保存」をクリック

- SMTP 認証を必須にする
今回はチェックしない
- 暗号化
チェックする

全て入力後「保存」をクリック

![012.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154569/198d79d1-d5db-b5d7-2880-3dd5e86e9b1e.png)

### メールの送信チェック

`sudo bash -x /root/test_mail.sh foo@example.com "-v"` の実行結果

sendmail.mc の結果

```bash
foo@example.com... Connecting to smtp-relay.gmail.com. via relay...
220 smtp-relay.gmail.com ESMTP q123-20020a0de781000000b006048ac4286esm46191ywe.71 - gsmtp
>>> EHLO example.com.
250-smtp-relay.gmail.com at your service, [XXX.XXX.XXX.XXX]
250-SIZE 157286400
250-8BITMIME
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8
>>> STARTTLS
220 2.0.0 Ready to start TLS
>>> EHLO example.com.
250-smtp-relay.gmail.com at your service, [XXX.XXX.XXX.XXX]
250-SIZE 157286400
250-8BITMIME
250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8
>>> MAIL From:<smmta@example.com> SIZE=95 AUTH=smmta@example.com.
250 2.1.0 OK q123-20020a0de781000000b006048ac4286esm46191ywe.71 - gsmtp
>>> RCPT To:<foo@example.com>
>>> DATA
250 2.1.5 OK q123-20020a0de781000000b006048ac4286esm46191ywe.71 - gsmtp
354  Go ahead q123-20020a0de781000000b006048ac4286esm46191ywe.71 - gsmtp
>>> .
250 2.0.0 OK  1708757786 q123-20020a0de781000000b006048ac4286esm46191ywe.71 - gsmtp
foo@example.com... Sent (OK  1708757786 q123-20020a0de781000000b006048ac4286esm46191ywe.71 - gsmtp)
Closing connection to smtp-relay.gmail.com.
>>> QUIT
221 2.0.0 closing connection q123-20020a0de781000000b006048ac4286esm46191ywe.71 - gsmtp
```

うまくいっているようです。

### 届いたメールの確認

![014.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154569/5920c2d4-b4af-df31-4705-2a29f135c8a9.png)

明示的に SPF や DKIM を設定していませんが、gmail ではいずれも PASS という判定になっています。

<details><summary>届いた内容のメール情報</summary>

:::note info
`reverse-lookup.example.com` は Google 側のサーバで IP からの逆引きで得られた FQDN です。
`*` は任意のアルファベットとアラビア数字 ( [A-Za-z0-9] ) です。

:::

```bash
Delivered-To: foo@example.com
Received: by XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX with SMTP id gx9csp512376mdb;
        Fri, 23 Feb 2024 22:56:28 -0800 (PST)
X-Received: by XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX with SMTP id m145-20020a0dca97000000b006086ea5d555mr1776901ywd.41.1708757787768;
        Fri, 23 Feb 2024 22:56:27 -0800 (PST)
ARC-Seal: i=1; a=rsa-sha256; t=1708757787; cv=none;
        d=google.com; s=arc-20160816;
        b=******************************************************************
         *************+************************************+*************+***
         ********************************+************+**********************
         **************/*****************************************************
         ***************************+****************************************
         ****==
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816;
        h=subject:to:from:date:message-id:dkim-signature;
        bh=******************/*+**********************=;
        fh=*******/************+*******+**************=;
        b=*****+******/******************************+****************/*****
         ******/*************************************************+***********
         **+*********************************************************+*******
         ********************************************************************
         +*****************************************/+************************
         ****==;
        dara=google.com
ARC-Authentication-Results: i=1; mx.google.com;
       dkim=pass header.i=@example.com header.s=google header.b=gh7btfLD;
       spf=pass (google.com: domain of smmta@example.com designates XXX.XXX.XXX.XXX as permitted sender) smtp.mailfrom=smmta@example.com
Return-Path: <smmta@example.com>
Received: from mail-XXX-XXX.google.com (mail-XXX-XXX.google.com. [XXX.XXX.XXX.XXX])
        by mx.google.com with SMTPS id g12-20020a0ddd0c000000b00608d9fb3b62sor242ywe.19.2024.02.23.22.56.26
        for <foo@example.com>
        (Google Transport Security);
        Fri, 23 Feb 2024 22:56:27 -0800 (PST)
Received-SPF: pass (google.com: domain of smmta@example.com designates XXX.XXX.XXX.XXX as permitted sender) client-ip=XXX.XXX.XXX.XXX;
Authentication-Results: mx.google.com;
       dkim=pass header.i=@example.com header.s=google header.b=gh7btfLD;
       spf=pass (google.com: domain of smmta@example.com designates XXX.XXX.XXX.XXX as permitted sender) smtp.mailfrom=smmta@example.com
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=example.com; s=google; t=1708757786; x=1709362586; darn=example.com;
        h=subject:to:from:date:message-id:from:to:cc:subject:date:message-id
         :reply-to;
        bh=******************/*+**********************=;
        b=***************************************/**************************
         ********+****************************/******************************
         *************************************=
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=1e100.net; s=20230601; t=1708757786; x=1709362586;
        h=subject:to:from:date:message-id:x-gm-message-state:from:to:cc
         :subject:date:message-id:reply-to;
        bh=******************/*+**********************=;
        b=**+********+************************************++****************
         ********************************************************************
         ************************/***********/*******************************
         ***********************************************+**************+*****
         **************+*****************************************************
         ****==
X-Gm-Message-State: *******************+********************************/*** ********************************+********+********************************* *****+****************/***************+*****=
X-Google-Smtp-Source: AGHT+IEzwMYreqav2RcKboP1394uLjee7F7XDa7Se8U0XtlBBO1C27WcrZjQK209krmfwGfvSNr5ty+ml9jU
X-Received: by XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX with SMTP id df8-20020a05690c0f8800b0060820df8b15mr2056183ywb.3.1708757786345;
        Fri, 23 Feb 2024 22:56:26 -0800 (PST)
Return-Path: <smmta@example.com>
Received: from example.com. (reverse-lookup.example.com. [XXX.XXX.XXX.XXX])
        by smtp-relay.gmail.com with ESMTPS id q123-20020a0de781000000b006048ac4286esm46191ywe.71.2024.02.23.22.56.25
        for <foo@example.com>
        (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
        Fri, 23 Feb 2024 22:56:26 -0800 (PST)
X-Relaying-Domain: example.com
Message-ID: <65d9931a.0d0a0220.25b0a.3494SMTPIN_ADDED_BROKEN@mx.google.com>
X-Google-Original-Message-ID: <202402240656.41O6uN8R151018@example.com.>
Received: (from smmta@localhost) by example.com. (8.15.2/8.15.2/Debian-18) id 41O6uN8R151018 for foo@example.com; Sat, 24 Feb 2024 06:56:23 GMT
Date: Sat, 24 Feb 2024 06:56:23 GMT
From: Mail Transfer Agent <smmta@example.com>
To: <foo@example.com>
Subject: test ( by root at 2024/02/24 15:55:22 )

This is a test mail.
```

</details>

`@gmail.com` 宛てのメールも上図のような結果になっていることを確認。

投げられるメールの特徴としては `Message-ID` に `SMTPIN_ADDED_BROKEN@mx.google.com` という文字列が含まれている点です。

# 設定の効果

SPF/DKIM/DMARC 関係の情報に注目してみます。

SPF/DKIM/DMARC の情報については以下の URL を参照。

https://ent.iij.ad.jp/articles/172/

## DNS の対応

今回検証した実ドメインは、さくらインターネットで提供している DNS を利用しています。
今回確認した限りでは ACME DNS-01 の検証に対応しており、challenge が成功したら spf レコードの書き換えを行ってくれるようです。
当然ながら DNS を提供しているサービスによっては ACME DNS-01 に対応していない場合もありますので注意。

## SPF

今回の検証環境では以下のような情報が設定されているようです。

```bash
dig txt example.com

(以下レスポンスの抜粋)
example.com.             3600    IN      TXT     "v=spf1 include:_spf.example.com include:_spf.google.com ~all"
```

`~all` とされているので、「それ以外から送信された場合は softfail とする」となります。

## DKIM

例えば `dig txt <selector>._domainkey.example.com.` のような形式で DNS から引っ張ってきます
`<selector>` で指定する文字列は、メールの `DKIM-Signature` ヘッダ内に含まれる、`s=` の箇所で指定されています。
今回の検証環境では以下のような情報が設定されているようです。

:::note info
`<selector>` に指定している箇所は実際には文字列 ( [A-Za-z0-9]+ ) です。
:::

```bash
dig txt <selector>._domainkey.example.com.

(以下レスポンスの抜粋)

<selector>._domainkey.example.com. 3600 IN   TXT     "v=DKIM1; k=rsa; p==********************************************************************+************************************/********/*********************************+***********/********************************+***********+**********"
```

## DMARC

`dig txt _dmarc.example.com` の結果は空でしたが、gmail 側で独自に DMARC ポリシーを設定している余地はあります。

## ARC

ARCについては以下の URL を参照しました。

https://www.naritai.jp/technology_arc.html

詳細は調べてませんが、メール内に `ARC-` ヘッダが付きます。

# 結論 ?

とりあえず、google で提供している機能を使えば大体のケースにおいて使えるとは思います。
但し、より厳密な制御を求めるのであれば DNS も含めたメールシステムの微調整は必要なようです。