Postfixで外部のサーバでメールをリレーさせる

# はじめに

[この記事](https://qiita.com/example/items/e394fb9e027892e9a2a4#logwatchでgmail宛にログを投げる)に記載したように、ubuntu 16.04 LTSで構築したVPSにて、LogwatchのレポートをGmailへ投げつけるようにcronを回している。ただ、DNSのTXTレコードにSPF[^1]を設定してもGoogleさんは次のように叱ってくることがある。

```/var/log/mail.log
Oct 31 06:25:04 conoha postfix/pickup[10085]: 33B788032C: uid=0 from=<root>
Oct 31 06:25:04 conoha postfix/cleanup[10886]: 33B788032C: message-id=<20171030212504.33B788032C@example.com>
Oct 31 06:25:04 conoha postfix/qmgr[8028]: 33B788032C: from=<root@example.com>, size=7913, nrcpt=1 (queue active)
Oct 31 06:25:06 conoha postfix/smtp[10890]: 33B788032C: to=<exapmple@gmail.com>, relay=gmail-smtp-in.l.google.com[2404:6800:4008:c02::1a]:25, delay=2.5, delays=0.7/0.07/0.6/1.1, dsn=5.7.1, status=bounced (host gmail-smtp-in.l.google.com[2404:6800:4008:c02::1a] said: 550-5.7.1 This message does not have authentication information or fails to pass 550-5.7.1 authentication checks. To best protect our users from spam, the 550-5.7.1 message has been blocked. Please visit 550-5.7.1  https://support.google.com/mail/answer/81126#authentication for more 550 5.7.1 information. t77si11719245pfa.185 - gsmtp (in reply to end of DATA command))
Oct 31 06:25:06 conoha postfix/cleanup[10886]: 044378032D: message-id=<20171030212506.044378032D@example.com>
Oct 31 06:25:06 conoha postfix/bounce[10949]: 33B788032C: sender non-delivery notification: 044378032D
Oct 31 06:25:06 conoha postfix/qmgr[8028]: 044378032D: from=<>, size=10471, nrcpt=1 (queue active)
Oct 31 06:25:06 conoha postfix/qmgr[8028]: 33B788032C: removed
Oct 31 06:25:06 conoha postfix/local[10950]: 044378032D: to=<root@example.com>, relay=local, delay=0.02, delays=0/0.01/0/0.01, dsn=2.0.0, status=sent (delivered to mailbox)
Oct 31 06:25:06 conoha postfix/qmgr[8028]: 044378032D: removed
```
要約すると「お前のメールは信用ならんのでリレーしない。首を洗って出直せこのクソビッチ。」ということ。

これにより、GoogleでドロップされてGmailまで届くのが大体2日に1回程度と確率的になってしまっている。SPFに加えてDKIMに対応するのも嫌なので、別途契約しているさくらのメールボックスから専用のアドレスを払い出し、SMTP-AUTHを必須とした外部リレーサーバとして利用することにした。さくらの信頼性に依存してしまえということだ。ありがとうさくら。

この記事ではPostfixのローカルサーバがメールを外部へ転送する際、特定の外部リレーサーバを必ず利用するように設定することを目的とする。と、共に、細々と古（いにしえ）のPostfixの設定を掘り起こし、設定ファイル内に出てくるオプションの意味を簡単に解説しておこう。

[^1]: Sender Policy Framework。メールの送信ドメインと送信元IPアドレスが一致してるかどうかをDNSのTXTレコードを参照して確認をとる。

# 外部リレーサーバの設定方法

## 設定準備・設定ポリシ
前提として、自ノードはexample.comとしてDNSのAレコードに登録されているものと仮定する。

### 外部リレーサーバ
まずはSMTP-AUTHの使える外部サービスを準備しておこう。筆者はさくらのメールボックスを利用しているが、
この記事では、メールサーバのホスト名などは以下と仮定する
- ホスト名: example.sakura.ne.jp
- サブミッションポート: 587
- SMTP-AUTH: CRAM-MD5, PLAIN, LOGIN
- STARTTLS対応

当該のメールサーバにおいては、セキュリティ面からリレー専用のユーザを1つ作っておくのが良いだろう。今回は、以下と仮定しておく。
- ID: sample_user
- Password: password

もちろん、GoogleでGmail用のアプリパスワードを払い出せばSMTP-AUTHが使えるサーバとしてsmtp.gmail.comが利用できる。ただし、そのアプリパスワードであなたの他のメールにIMAPアクセスも可能なことを忘れてはいけない。セキュリティを気にするならば別のGmailアカウントを取得すべきだ。

### 設定ポリシ
文章で書けば以下の条件にマッチした場合はexample.sakura.ne.jp:587へリレーさせる。

- 自分自身(localhost)が送信元 (他のノードからのメールではない。dockerコンテナも除外している。)
- 自ドメイン(example.com)宛「以外」のメール。自ドメイン宛のメールは転送せずローカルメールボックスへ格納する。

詳細にどこがどうその設定にマッピングしているのかは、後ほど解説する。


## 設定ファイルの作成
ざっくりと設定ファイルは以下の通りだ。コピペしてexample.com/example.sakura.ne.jpと書いてある部分などを自分の環境に合わせて修正するだけでもおそらく動く。中身の解説は後述。

```/var/postfix/main.cf
compatibility_level = 2

myhostname = example.com
mydomain = example.com
myorigin = $mydomain

smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = yes
append_at_myorigin = yes

readme_directory = no

# TLS parameters
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_use_tls=yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.

smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydestination = $myhostname localhost.localdomain localhost.$mydomain localhost $mydomain
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
#inet_interfaces = all
inet_interfaces = localhost
inet_protocols = all

relayhost = [example.sakura.ne.jp]:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_mechanism_filter = cram-md5, plain, login
smtp_sasl_tls_security_options =  noanonymous, noplaintext
smtp_tls_security_level = may
smtp_tls_CApath = /etc/pki/tls/certs/ca-bundle.crt
```
## SMTP-AUTHのユーザ登録
SMPT-AUTHのSASL認証のユーザ・パスワードを登録しよう。まずは、`/etc/postfix/sasl_passwd`を平文で作成する。

```/etc/postfix/sasl_passwd
example.sakura.ne.jp sample_user@example.sakura.ne.jp:password
```

これをpostmapによりハッシュ化する。

```
$ sudo postmap /etc/postfix/sasl_passwd
```
これにより`/etc/postfix/sasl_passwd.db`が作成される。

## 結果
Postfixデーモンを再起動してLogwatchからメールを投げてみる。
```
$ sudo /etc/init.d/postfix restart
$ sudo /etc/cron.daily/00logwatch
```

すると無事にさくらをリレーしてメールが送信されているのが確認できる。

```
Nov  2 06:25:03 conoha postfix/pickup[10318]: B620980574: uid=0 from=<root>
Nov  2 06:25:03 conoha postfix/cleanup[10537]: B620980574: message-id=<20171101212503.B620980574@example.com>
Nov  2 06:25:03 conoha postfix/qmgr[1507]: B620980574: from=<root@example.com>, size=7383, nrcpt=1 (queue active)
Nov  2 06:25:04 conoha postfix/smtp[10545]: B620980574: to=<example@gmail.com>, relay=example.sakura.ne.jp[49.212.235.231]:587, delay=1.3, delays=0.71/0.07/0.33/0.15, dsn=2.0.0, status=sent (250 2.0.0 vA1LP4Hb090197 Message accepted for delivery)
Nov  2 06:25:04 conoha postfix/qmgr[1507]: B620980574: removed
Nov  3 06:25:03 conoha postfix/pickup[22997]: 7D9E880574: uid=0 from=<root>
Nov  3 06:25:03 conoha postfix/cleanup[23546]: 7D9E880574: message-id=<20171102212503.7D9E880574@example.com>
Nov  3 06:25:03 conoha postfix/qmgr[1507]: 7D9E880574: from=<root@example.com>, size=6499, nrcpt=1 (queue active)
Nov  3 06:25:04 conoha postfix/smtp[23550]: 7D9E880574: to=<example@gmail.com>, relay=example.sakura.ne.jp[49.212.235.231]:587, delay=1.1, delays=0.56/0.09/0.28/0.16, dsn=2.0.0, status=sent (250 2.0.0 vA2LP3VX074155 Message accepted for delivery)
Nov  3 06:25:04 conoha postfix/qmgr[1507]: 7D9E880574: removed
```

とりあえずこれでひとまずOK。

# 設定ファイル内のオプションについて

上記設定ファイルで決められたリレーのポリシなどについて理解を深めるため、重要なオプションを解説する。

## リレー周り
### `smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination`
Postfix 2.10以降に新設されたオプション。Postfixが受け取ったメールを他のサーバへリレーさせるポリシを規定する。ここでは、後述する`$mynetworks`で規定する信頼できるネットワークレンジ、SASL認証を通過したもの、なんか良くわかんない宛先へのリレーはしばらく延期、という設定。最低限でもこれくらい(ubuntuデフォルト)。

### `alias_maps = hash:/etc/aliases`, `alias_database = hash:/etc/aliases`
いわゆるalias、別名設定。今回は利用していないが存在しないユーザ宛のメールを配送するなどの時に使う。企業で公開しているinfo@example.comとかを全部特定ユーザに振り分けるとか。

### `mydestination = $myhostname localhost.localdomain localhost.$mydomain localhost $mydomain`
このホストがリレーの終端であると判断する、ドメイン名の設定。今回は、example.comというドメインのサーバから、example.sakura.ne.jpという異なるドメインのリレーサーバを利用しているので、example.com宛のメールはこのホストで終端すれば良い。一方で、example.comのAレコードはこのホストを指している場合 [^2]、この設定ではexample.com宛のメールはこのホストから出ていかず、外部リレーサーバを使った配送を行うことができない。このときは、以下のように`myhostname`を明示的にconoha.example.com、`mydestination`から`mydomain`宛の設定を削除とするなどして、~@example.comを宛先とするメールは外部メールサーバへ送達されるようにする。

```/etc/postfix/
mydomain = example.com
myhostname = conoha.$mydomain
myorigin = $myhostname
mydestination = $myhostname, localhost.$mydomain, localhost.localdomain, localhost
```

[^2]: MXレコードはGoogle Appsなど別のホストを指しているとする。


### `mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128`
信頼できる、とする自ネットワークのアドレスレンジ指定。

### `inet_interfaces = localhost`
このホストがメールを受け取るネットワークインターフェイスアドレス。localhostもしくは127.0.0.1とすることで自ホストから発出にしか対応しないようにしている。

### `inet_protocols = all`
PostfixがIPv4つかうかIPv6つかうかっていう話。`all`は`ipv4,ipv6`もしくは`IPv4`と等価（OS依存）。

### `relayhost = [example.sakura.ne.jp]:587`
リレーサーバのホスト名およびポートを指定する。`[]`をつけてホスト名を指定するとホストのMXレコードを無視する。つまり、example.comに対してAレコードの示すIPアドレスと、MXレコードの示すFQDNが異なるホストである場合などに使用する。IPアドレス直書きも可能。

## SASL(SMTP-AUTHの認証機構)周り
### `smtp_sasl_auth_enable = yes`
Postfix SMTPクライアントとしてのSASL認証をONにする。noにするとOFF。

### `smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd`
SASL認証先のホスト名（リレーサーバ）と、ID・パスワードを記入するファイル名を指定。postmapでハッシュ掛ける。

### `smtp_sasl_mechanism_filter = cram-md5, plain, login`
SASLで使うPostfix SMTPクライアント・リレーサーバ間でのSASL認証メカニズムの候補。ここで指定したものとリレーサーバ間で共通のものを使うことになる。`!GSSAPI`とかするとKerberosとかを拒否する。

### `smtp_sasl_tls_security_options =  noanonymous, noplaintext`
Postfix SMTPクライアントで使う認証メカニズムを指定する。
- noplaintext: 平文パスワードを使う認証方法を拒否
- noactive: active攻撃を受ける認証方法を拒否
- nodictionary: passive攻撃を受ける認証方法を拒否
- noanonymous: 匿名認証を拒否
- mutual_auth: 相互認証を提供する方法のみ許可

### `smtp_tls_security_level = may`
`may`とすると、リレーサーバがTLS暗号化をサポートする場合、TLSサーバを利用する。

### `smtp_tls_CApath = /etc/pki/tls/certs/ca-bundle.crt`
CA証明書の場所を指定。

# まとめ

Postfixをこのご時世でまだ触る羽目になると思わなかった…ものの、VPSとか建ててWebサービスを運用しているとこの辺は都度必要になってくるので備忘録として役に立てば幸い。