Azure にインストールした postfix に SMTP-Auth の設定を追加する

「Azure にインストールした postfix を SMTP でテストする」
https://qiita.com/nanbuwks/items/fc4a6b0daba712f6bc11
では、telnet で SMTP プロトコルを叩いてメールを送信しましたが、パスワードも何もなしでメールが送れてしまいました。
そのままだと SPAM の踏み台になってしまうので、認証の仕組みを導入することにします。

# 環境

- Ubuntu 22.04
- Postfix

「Azure で postfix のインストールを試す」
https://qiita.com/nanbuwks/items/38b54aaf8c3ecfdbb3cc
「Azure/Ubuntu にインストールした postfix / dovecot を Maildir 対応にする」
https://qiita.com/nanbuwks/items/10e6dd1224ce0c3f76af


で設定したものです。


# EHLO コマンド

先の telnet で 接続したときには、最初に

```
HELO localhost
```

として接続し、以下のように返答が返ってきました。
```
￼250 test-azure-gen.ugx1l5hl2pbu5oitg4vrqao0jd.phxx.internal.cloudapp.net
```

もともとの SMTP には認証の仕組みがありませんが、 ESMTP コマンド (SMTP サービス拡張) でサーバーに対してクライアントを認証するために使用される AUTHコマンドが定義されています。

ESMTPコマンドを使うには、最初の接続時に `HELO` ではなく `EHLO` コマンドを使います。

先の実験と同じサーバで以下のように入力すると
```
EHLO localhost
```
以下のように返答が返ってきます。

```
250-test-azure-gen.ugx1l5hl2pbu5oitg4vrqao0jd.phxx.internal.cloudapp.net
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250-SMTPUTF8
250 CHUNKING
```

AUTH コマンドが使用できる場合、応答に AUTH フレーズが含まれます。しかしながら今回は見当たらないので SMTP-Auth が設定されていないことがわかります。
以下で、postfix に SMTP-Authを設定していきます。


# 現在の postfix の設定

postfix は /etc/postfix/main.cf で設定されていますが、コメントが多く一覧性が悪いです。以下のようにして設定部分だけを抜き出すことができます。

```
$ postconf -n
```

現在の設定内容が表示されます。

```

alias_database = hash:/etc/aliases
alias_maps = hash:/etc/aliases
append_dot_mydomain = no
biff = no
compatibility_level = 2
home_mailbox = Maildir/
inet_interfaces = all
inet_protocols = all
mailbox_size_limit = 0
mydestination = $myhostname, test-azure-gen.ugx1l5hl2pbu5oitg4vrqao0jd.phxx.internal.cloudapp.net, localhost.ugx1l5hl2pbu5oitg4vrqao0jd.phxx.internal.cloudapp.net, , localhost
myhostname = test-azure-gen.ugx1l5hl2pbu5oitg4vrqao0jd.phxx.internal.cloudapp.net
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
myorigin = /etc/mailname
readme_directory = no
recipient_delimiter = +
relayhost =
setgid_group = postdrop
smtp_tls_CApath = /etc/ssl/certs
smtp_tls_security_level = may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
smtpd_tls_cert_file = /etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file = /etc/ssl/private/ssl-cert-snakeoil.key
smtpd_tls_security_level = may
```

# SASL

認証や暗号化を行うにあたり、postfix は自前で処理を行うことに対して否定的な立場をとっています。
cf.,
https://www.postfix-jp.info/trans-2.3/jhtml/TLS_README.html

SASL は RFC4422 で定められている認証やセキュリティに関する処理で、これを呼び出すことで postfix の自前処理でのセキュリティホールを回避します。
現在インストールしている postfix において、利用可能な SASL プラグインを以下のようにしてリストアップします。

```
$ postconf -a
```

Postfixで使用できる SASL 実装としてはCyrus SASLとDovecot SASL があり、その両方が利用可能であることがわかります。
 
```
cyrus
dovecot
```

今回は、Dovecot SASLを使うことにします。

## SASL メカニズム

SASLには 一連の要求と応答をモデル化したものが SASL メカニズムとして IANA にて定義されています。
https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml

このうち、SMTP でよく使われる SASL メカニズムには以下のものがありますが、


- PLAIN
- LOGIN
- CRAM-MD5


これらのうち LOGIN および CRAM-MD5 は廃止となっています。そのため最新の IANA にあわせて設定するとなると PLAIN を用いることになります。

# postfix の設定


/etc/postfix/main.cf の末尾に、以下を加えます。

```

# SMTP-AUTH
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
broken_sasl_auth_clients = yes
smtpd_recipient_restrictions =
    permit_mynetworks 
    permit_sasl_authenticated
    reject_unauth_destination                               
```

最後の、smtpd_recipient_restictions はメール受付の可否の設定です。

- permit_mynetworks 
  - mynetworks で指定されたネットワークからのメールを許可    
- permit_sasl_authenticated
  - SASL 認証を通過した場合許可    
- reject_unauth_destination 
  - それ以外を拒否

これを保存して、設定チェックをします。
```
$ sudo postfix check
postfix/postfix-script: warning: symlink leaves directory: /etc/postfix/./makedefs.out
postfix/postfix-script: warning: /var/spool/postfix/etc/hosts and /etc/hosts differ
```
と出たので、 /etc/hosts を /var/spool/postfix/etc/hosts  にコピーします。

```
$ sudo cp /etc/hosts /var/spool/postfix/etc/hosts 
```
:::note
この設定は、今回の SMTP-Auth とは関係ありませんが、別記事
「Azure/Ubuntu/Apache2 で PHP と VirtulaHost を設定する」
https://qiita.com/nanbuwks/items/d3a1a148e597f5c5df1c
で行った、 /etc/hosts に example.com を追加した設定を postfix に反映するためのものです。
:::

# dovecot の設定

/etc/dovecot/conf.d/10-master.conf の中の、  service auth の内容は以下のようになっています。


```

service auth {
  # auth_socket_path points to this userdb socket by default. It's typically
  # used by dovecot-lda, doveadm, possibly imap process, etc. Users that have
  # full permissions to this socket are able to get a list of all usernames and
  # get the results of everyone's userdb lookups.
  #
  # The default 0666 mode allows anyone to connect to the socket, but the
  # userdb lookups will succeed only if the userdb returns an "uid" field that
  # matches the caller process's UID. Also if caller's uid or gid matches the
  # socket's uid or gid the lookup succeeds. Anything else causes a failure.
  #
  # To give the caller full permissions to lookup all users, set the mode to
  # something else than 0666 and Dovecot lets the kernel enforce the
  # permissions (e.g. 0777 allows everyone full permissions).
  unix_listener auth-userdb {
    #mode = 0666
    #user = 
    #group = 
  }

  # Postfix smtp-auth
  #unix_listener /var/spool/postfix/private/auth {
  #  mode = 0666
  #}

  # Auth process is run as this user.
  #user = $default_internal_user
}
```

service auth の中身が以下であるようにします。


```

  unix_listener auth-userdb {
    #mode = 0666
    #user = 
    #group = 
  }

  # Postfix smtp-auth
  unix_listener /var/spool/postfix/private/auth {
    mode = 0666
    user = postfix
    group = postfix
  }


```
dovecot の設定を反映します。


```
$ sudo service dovecot restart

```

# telnet でテスト

ここまで設定して、telnet で接続してみます。

```
$ telnet localhost 25
```
postfix から反応が返ってきて、問題なく接続できています
```
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
```
EHLOコマンドを送ります。
```
EHLO localhost
```
反応が返ってきます。
```
220 test-azure-gen.ugx1l5hl2pbu5oitg4vrqao0jd.phxx.internal.cloudapp.net ESMTP Postfix (Ubuntu)
250-test-azure-gen.ugx1l5hl2pbu5oitg4vrqao0jd.phxx.internal.cloudapp.net
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-AUTH PLAIN
250-AUTH=PLAIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250-SMTPUTF8
250 CHUNKING
```
以下が増えていることがわかります。

- `250-AUTH PLAIN`
- `250-AUTH=PLAIN`


おや、 PLAIN 認証らしきものが2つありますね。これについて、下側のものは
```
broken_sasl_auth_clients = yes
```
設定が有効になっているためです。これは旧い Outlook Express にて、`250-AUTH=PLAIN` という応答でないと認識をしない問題に対応するためのものです。
接続を一旦終了します。

```
QUIT
221 2.0.0 Bye
Connection closed by foreign host.
```
# PLAIN 認証を使ってみる。



AUTH PLAIN コマンドで送るパラメータとして、以下のいずれかのフレーズを送る必要があります。

- `ユーザ名\0ユーザ名\0パスワード` を BASE64  エンコードしたもの
- `\0ユーザ名\0パスワード` を BASE64 エンコードしたもの

以下のようにして、あらかじめ用意しておきます。
```
$ printf "%s\0%s\0%s" example example hogehogefugafuga | openssl base64 -e | tr -d '\n'; echo
```
出力されたものをコピーしておきます。

```
ZXhhbXBsZQBleGFtcGxlAGhvZ2Vob2dlZnVnYWZ1Z2E=
```


再度、telnet で接続して EHLO を送ります。

```
$ telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

EHLO localhost
220 test-azure-gen.ugx1l5hl2pbu5oitg4vrqao0jd.phxx.internal.cloudapp.net ESMTP Postfix (Ubuntu)
250-test-azure-gen.ugx1l5hl2pbu5oitg4vrqao0jd.phxx.internal.cloudapp.net
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-AUTH PLAIN
250-AUTH=PLAIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250-SMTPUTF8
250 CHUNKING
```

```
AUTH PLAIN ZXhhbXBsZQBleGFtcGxlAGhvZ2Vob2dlZnVnYWZ1Z2E=
235 2.7.0 Authentication successful


```
認証できました。

なお、
```
AUTH PLAIN
334
ZXhhbXBsZQBleGFtcGxlAGhvZ2Vob2dlZnVnYWZ1Z2E=
235 2.7.0 Authentication successful

```
のように、AUTH PLAIN と BASE64フレーズは2行に分けてもOKです。