デーモンの起動しないSMTPクライアントの msmtp を試す(sSMTP乗り換え)

sSMTP から msmtp へ

現在メールを受け取らずメールを送信するだけのホストではsSMTPをよく使っています．MTAはeximやPostfix等でも良いのですが，デーモンが起動しないのでその分セキュアで設定も楽です．
しかし，sSMTPは最近メンテされていません，Debian などのディストリビューションではまだメンテナンスされていますが新機能が入ったりはしないでしょう．

sSMTP Alternatives and Similar Software – AlternativeTo.net

ssmtp is unmaintained. Consider using something like msmtp instead.

バージョンも据え置きでUpstreamも無さそう

$ w3m -dump https://packages.qa.debian.org/s/ssmtp.html|grep versions -A11
versions ... ...pool

o-o-stable
save 2.64-7
oldstable
save 2.64-8
stable
save 2.64-8
unstable
save 2.64-8
Ubuntu
2.64-8ubuntu2
$ apt show ssmtp 2>/dev/null |grep Homepage
Homepage: http://packages.qa.debian.org/s/ssmtp.html

Debian Package Tracking System – ssmtp

ajwans/sSMTP: patches to sSMTP

そこで，alternativeto.net で挙げられている msmtp を試してみました．

msmtp – about

以下では色々試していますが，システムメールを送るだけなら msmtp, msmtp-mta pkg を導入，/etc/msmtprc の設定だけでokです．

msmtp の導入

Debian sid/Ubuntu 18.04 LTS などではパッケージがあったのでこれを導入しました．

$ sudo apt install msmtp

mstpの設定

設定例が /usr/share/doc/msmtp/examples/ 以下にあるので
システム設定の場合は msmtprc-system.example を `/etc/msmtprc にcpして設定．
ユーザ設定の場合は msmtprc-user.example を ~/.msmtprc にcpして設定する．

システムでの設定例

以下の例では root が読み書きできて，一般ユーザのmatokenアカウントは読み込みが可能な設定ファイル /etc/msmtprc を作成している．
defaults セクションに共通設定を書き，account セクションにメールサーバ固有の設定を書く．account セクションは複数書けて，account default に規定account を指定する．以下の例ではgmail が規定値となる．

/etc/msmtprc の作成

$ sudo install /dev/null /etc/msmtprc -o root -g root -m 600
$ sudo setfacl -m u:matoken:r /etc/msmtprc
$ getfacl /etc/msmtprc
getfacl: Removing leading '/' from absolute path names
# file: etc/msmtprc
# owner: root
# group: root
user::rw-
user:matoken:r--
group::---
mask::r--
other::---

$ sudo vi /etc/msmtprc
$ sudo grep -v ^# /etc/msmtprc

defaults
syslog on
aliases /etc/aliases

tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
auth on
syslog LOG_MAIL

account gmail
host smtp.gmail.com
port 587
from example@gmail.com
user example
password porseypacdoadwif

account default : gmail

※gmailの多要素認証を利用している場合は以下からアプリパスワードを生成する．(使い回しができちゃうけれど流出したときの影響を考えて設定毎に発行する)

アプリ パスワード

ユーザでの設定

ユーザの設定ファイルは`~/.msmtprc` 若しくは $XDG_CONFIG_HOME/msmtp/config でシステムと違い，alias file と log を適当なファイルにした．
アカウントも増やしてみた．
gmailのアプリパスワードも新たに発行した．

~/.msmtprc の作成

$ install /dev/null ~/.msmtprc -o matoken -g matoken -m 600
$ vi ~/.msmtprc
$ grep -v ^# ~/.msmtprc

defaults
logfile ~/.msmtp.log
aliases ~/.msmtp.aliases

tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
auth on
syslog LOG_MAIL

account gmail
host smtp.gmail.com
port 587
from example@gmail.com
user example
password igeinleedafchygy

account yandex
tls_starttls off
host smtp.yandex.com
port 465
from example@yandex.com
user example
password throjfeekdewxeib

account nifty
tls_starttls off
host smtp.nifty.com
port 465
from example@nifty.com
user aaa99999
password 9Z#9999X

account default : gmail

送信テスト

msmtp コマンドで送信テストが出来る．
-a オプションでアカウントが指定できる．
-C オプションで設定ファイルが指定できる．(システムとユーザの設定を両方テストする)

$ echo "hello system msmtp" | msmtp -C /etc/msmtprc matohara@gmail.com
$ echo "hello user msmtp" | msmtp -C ~/.msmtprc -a gmail matohara@gmail.com

大抵のMTAでは . でmail body 終了となるが，msmtpでは`.`は普通にbody に含まれる．Ctrl + d で終了．

$ msmtp -a gmail matohara@gmail.com
Subject: hello

msmtp test
.
dot では終了にならない
Ctrl+d で終了
^d

aliases file

msmtp設定ファイルの alias で設定できる．

$ grep alias ~/.msmtprc
aliases ~/.msmtp.alias
$ sudo grep alias /etc/msmtprc
aliases /etc/aliases

このalias fileは通常のaliases ファイルと同じ形式の plain text で newaliases コマンドでの変換は不要．

メールアドレスは複数指定できる．default という名前は規定アドレスとなる．

root: hoge@example.com, fuga@example.com
default: piyo@example.com

以前のaliases ファイルを流用して以下のようにしてみたところ，

# /etc/aliases
mailer-daemon: postmaster
postmaster: root
nobody: root
hostmaster: root
usenet: root
news: root
webmaster: root
www: root
ftp: root
abuse: root
noc: root
security: root
root: matohara@gmail.com
default: matohara@gmail.com

以下のようにエラーとなった．alias先はユーザ名ではエラーとなるよう．

$ echo hello | sudo msmtp -C /etc/msmtprc root
msmtp: /etc/aliases: line 2: invalid address 'postmaster'

以下のようにそれぞれにメールアドレスを指定すると期待した動作となった．

$ sudo sed -i -e 's/ root$\| postmaster$/matohara@gmail.com/' /etc/aliases
$ cat /etc/aliases
# /etc/aliases
mailer-daemon:matohara@gmail.com
postmaster:matohara@gmail.com
nobody:matohara@gmail.com
hostmaster:matohara@gmail.com
usenet:matohara@gmail.com
news:matohara@gmail.com
webmaster:matohara@gmail.com
www:matohara@gmail.com
ftp:matohara@gmail.com
abuse:matohara@gmail.com
noc:matohara@gmail.com
security:matohara@gmail.com
root: matohara@gmail.com
default: matohara@gmail.com

パスワードをGnuPG暗号化ファイルに格納して利用する

パスワードをgpgで暗号化したファイルに格納することが可能．この場合アカウントごとにファイルが必要なのが少し面倒．
password porseypacdoadwif の場合 porseypacdoadwif 部分だけを暗号化してファイルに格納する．(はじめpassword も含めてしまい失敗した)

$ grep ^password\ ~/.msmtprc | head -1 | awk {'print $2'} | gpg2 --default-recipient-self -e - > ~/.msmtp-password-gmail.gpg
$ grep ^password\ ~/.msmtprc | head -2 | tail -1 | awk {'print $2'} | gpg2 --default-recipient-self -e - > ~/.msmtp-password-yandex.gpg
$ grep ^password\ ~/.msmtprc | tail -1 | awk {'print $2'} | gpg2 --default-recipient-self -e - > ~/.msmtp-password-nifty.gpg

msmtprc の password を削除して，代わりに passwordeval gpg2 --no-tty -q -d 暗号ファイル と設定を変更する．

$ grep ^passworde ~/.msmtprc
passwordeval gpg2 --no-tty -q -d ~/.msmtp-password-gmail.gpg
passwordeval gpg2 --no-tty -q -d ~/.msmtp-password-yandex.gpg
passwordeval gpg2 --no-tty -q -d ~/.msmtp-password-nifty.gpg

後はこれまでのようにmsmtpを呼ぶとgpg-agentが起動する．

※システム側はシステムメールが送れなくなるのでこの設定をしないほうがいいと思う．

パスワードをopenssl暗号化ファイルに格納

システムメールでgpg agentを利用するのは多分無理なのでopensslで暗号化してみる．パスワードはNICのmac addressにしてある．気休めレベルだけど試しに設定．
マシンが乗っ取られた場合はどうしようもないけれど /etc/msmtprc, /etc/msmtp-password ファイル流出時の時間稼ぎくらいにはなる?
NIC が使っていないものも含め増減したり変更になった場合は復元できなくなるので注意．

mac address をパスワードとしてopensslで暗号化

$ umask 0077; grep ^password\ /etc/msmtprc | head -1 | awk {'print $2'} | sudo openssl enc -e -md sha256 -aes-256-cbc -a -A -salt -pbkdf2 -in /dev/stdin -out /etc/msmtp-password -pass pass:`hwinfo --network | grep 'Permanent HW Address' | sed 's/^.*: //' | sort -n | tr -d '\n'`

復元テスト

$ sudo openssl enc -d -md sha256 -aes-256-cbc -a -A -salt -pbkdf2 -in /etc/msmtp-password -pass pass:`hwinfo --network | grep 'Permanent HW Address' | sed 's/^.*: //' | sort -n | tr -d '\n'`
igeinleedafchygy

/etc/msmtprc の password を削除して passwordeval に書き換え

$ grep passworde /etc/msmtprc
passwordeval openssl enc -d -md sha256 -aes-256-cbc -a -A -salt -pbkdf2 -in /etc/msmtp-password -pass pass:`hwinfo --network | grep 'Permanent HW Address' | sed 's/^.*: //' | sort -n | tr -d '\n'`

※ifconfigやiproute2を使わずhwinfoを使ってPermanent HW Addressを使っているのはmac address偽装対策

MTA パッケージの導入

msmtp コマンドでメールが送信できるようになったら，msmtp-mta pkg を導入する．これでsendmailコマンドにシンボリックリンクが張られて同様に利用できるようになる．システムメールも飛ぶようになる．

$ sudo apt install msmtp-mta
$ ls -l /usr/sbin/sendmail
lrwxrwxrwx 1 root root 12 11月 29 2016 /usr/sbin/sendmail -> ../bin/msmtp

オフライン時にmsmtpを利用する

msmtpはオフライン時に実行するとエラーとなる．通常は常にオンラインなので問題はないが，持ち運びNotePCなどではオフラインオンラインが発生してメールの送信に失敗することがある．

$ echo 'hoge' | msmtp -a gmail matoken@gmail.com
msmtp: cannot locate host smtp.gmail.com: 名前またはサービスが不明です
msmtp: could not send mail (account gmail from /etc/msmtprc)

/usr/share/doc/msmtp/examples/ 以下の msmtpq 若しくは msmtpqueue に格納されているscriptを利用することでオフライン時にキューとして貯めておいてオンライン時に送信といったことが出来る．

どちらもsendmailと置き換えが可能なので，ネットワークダウン時に`/usr/sbin/sendmail`をこれに置き換えてオンライン時に戻してqueueをフラッシュするようにするといいかもしれない．

Debian/Ubuntuなら以下のあたりで，

/etc/network/if-down.d/
/usr/sbin/sendmail を msmtpq or msmtpqueue に向ける

/etc/network/if-pre-up.d/
/usr/sbin/sendmail を msmtp に向ける

/etc/network/if-up.d/
queueをフラッシュ

NetworkManagerだと /etc/NetworkManager/dispatcher.d/ 以下で同じことが出来る．

msmtpq

圧縮されているmsmtpqを展開して実行権を付与する

$ sudo gunzip /usr/share/doc/msmtp/examples/msmtpq/msmtpq.gz
$ sudo chmod 755 /usr/share/doc/msmtp/examples/msmtpq/msmtpq

使いやすい場所にシンボリックリンクを張る

$ sudo ln -s /usr/share/doc/msmtp/examples/msmtpq/msmtpq /usr/local/bin/msmtpq
$ sudo ln -s /usr/share/doc/msmtp/examples/msmtpq/msmtp-queue /usr/local/bin/msmtp-queue

queueとlogディレクトリを作成

$ install -d -m 700 ~/.msmtp.queue
$ install -d -m 700 ~/log

オフライン時に送信しようとする

$ echo 'msmtpq' | msmtpq -C ~/.msmtprc -a gmail matoken@gmail.com
mail for [ -C /home/matoken/.msmtprc -a gmail matoken@gmail.com ] : couldn't be sent - host not connected
enqueued mail as : [ 2018-09-18-06.21.48 ] ( -C /home/matoken/.msmtprc -a gmail matoken@gmail.com ) : successful

queueを確認する

$ msmtp-queue -d

mail num=[ 1 ] id=[ 2018-09-18-06.21.18 ]

mail num=[ 2 ] id=[ 2018-09-18-06.21.48 ]

queueをフラッシュする

$ msmtp-queue -r
mail [ 1 ] [ 2018-09-18-06.21.18 ] from queue ; send was successful ; purged from queue
mail [ 2 ] [ 2018-09-18-06.21.48 ] from queue ; send was successful ; purged from queue
$ msmtp-queue -d

no mail in queue

msmtpqueue

使いやすい場所にシンボリックリンクを張る

$ sudo ln -s /usr/share/doc/msmtp/examples/msmtpqueue/msmtp-enqueue.sh /usr/local/bin/msmtp-enqueue.sh
$ sudo ln -s /usr/share/doc/msmtp/examples/msmtpqueue/msmtp-listqueue.sh /usr/local/bin/msmtp-listqueue.sh
~$ sudo ln -s /usr/share/doc/msmtp/examples/msmtpqueue/msmtp-runqueue.sh /usr/local/bin/msmtp-runqueue.sh

`msmtp`コマンドの代わりに`msmtp-enqueue.sh`を使う

$ echo 'queue' | msmtp-enqueue.sh -C ~/.msmtprc -a gmail matoken@gmail.com

queueを確認する

$ msmtp-listqueue.sh
No mail in queue

今回のようにメールの内容がbodyだけでFrom, To, Subject が存在しないとこのscriptはうまく機能しない><

egrep -s --colour -h '(^From:|^To:|^Subject:)' "$i" || echo "No mail in queue";

直接`~/.msmtpqueue/`を確認するとqueueが溜まっているのがわかる

$ ls -lA ~/.msmtpqueue/
合計 8
-rw------- 1 matoken matoken 7 9月 18 00:15 2018-09-18-00.15.53.mail
-rw------- 1 matoken matoken 53 9月 18 00:15 2018-09-18-00.15.53.msmtp

Subjectの付いたメールがある時

$ msmtp-listqueue.sh
No mail in queue

Subject: offline><

オンライン時にqueueをフラッシュする

$ msmtp-runqueue.sh
*** Sending 2018-09-18-00.15.53.mail to -C /home/matoken/.msmtprc -a gmail matoken@gmail.com ...
2018-09-18-00.15.53.mail sent successfully
*** Sending 2018-09-18-00.27.21.mail to -C /home/matoken/.msmtprc -a gmail matoken@gmail.com ...
2018-09-18-00.27.21.mail sent successfully

＃sudoの場合も ~root/.msmtpqueue/ だった．

tips

gmailで以下のエラーとなった場合 tls_starttls on ( tls onのときの既定値? )と設定すると送信できた

$ echo "hello msmtp" | sudo msmtp -a gmail matohara@gmail.com
msmtp: envelope from address matohara@gmail.com not accepted by the server
msmtp: server message: 530 5.7.0 Must issue a STARTTLS command first. k126-v6sm14991126pgk.26 - gsmtp
msmtp: could not send mail (account gmail from /etc/msmtprc)

Yandexでメール送信時に帰ってこない場合 tls_starttls off と設定すると送信できた

niftyで以下のエラーとなった場合 tls_starttls off と設定すると送信できた

msmtp: network read error: 接続が相手からリセットされました
msmtp: could not send mail (account nifty from /etc/msmtprc)

試した環境

環境1(Raspberry Pi 2B/Raspbian stretch)

$ dpkg-query -W msmtp msmtp-mta gnupg2 openssl
gnupg2 2.1.18-8~deb9u2
msmtp 1.6.6-1
msmtp-mta 1.6.6-1
openssl 1.1.0f-3+deb9u2
$ lsb_release -d
Description: Raspbian GNU/Linux 9.4 (stretch)
$ uname -m
armv7l

環境2(Ubuntu 18.04 LTS)

$ dpkg-query -W msmtp msmtp-mta gnupg2 openssl
gnupg2 2.2.4-1ubuntu1.1
msmtp 1.6.6-1
msmtp-mta 1.6.6-1
openssl 1.1.0g-2ubuntu4.1
$ lsb_release -d
Description: Ubuntu 18.04.1 LTS
$ uname -m
x86_64

環境3(Debian sid)

$ dpkg-query -W msmtp msmtp-mta gnupg2 openssl
gnupg2 2.2.10-1
msmtp 1.6.7-1
msmtp-mta
openssl 1.1.1-1
$ lsb_release -d
Description: Debian GNU/Linux unstable (sid)
$ uname -m
x86_64