WSL に postfix を導入して Office365 宛のメールを中継して Python で加工する

## はじめに

調査が必要になったので、送信したメールを中継して加工したうえで Office365 へ転送する方法を試してみました。

WSL → telnet → メール送信 → postfix → Python で加工 → sendmail → Office365 のような感じです。

実現方法の確認程度なので、Windows 10 の WSL (Ubuntu 18.04 LTS) を環境として利用しています。
メールの送信も WSL（localhost）内からとして、postfix の設定も動作確認を優先したものとしています。

## 環境

Windows 10 Pro で WSL (Ubuntu 18.04 LTS) が利用できる環境からスタートしました。

[WSL（Windows Subsystem for Linux）の導入とUbuntuの初期設定](https://qiita.com/kerobot/items/b4f5472d71119506baaa)

* Windows 10 Pro
* WSL (Ubuntu 18.04 LTS)
* Postfix 3.3.0
* Python 3.6.9 + pip

## 参考にした情報

http://www.postfix.org/FILTER_README.html
https://qiita.com/kanedaq/items/aab362e20bdafea83fff

## WSL (Ubuntu 18.04 LTS) への Postfix の導入と中継設定

* postfix をインストールする

```bash
$ sudo apt install postfix
```

* postfix の設定ファイルをコピーして編集する

```bash
$ sudo cp /etc/postfix/main.cf.proto /etc/postfix/main.cf
$ sudo vi /etc/postfix/main.cf
```

* コメントを外したり設定を変更した個所のみ抜粋

```bash
# メールシステムのホスト名（FQDN）
myhostname = hostname.example.jp
# メールシステムのドメイン名
mydomain = example.jp
# メールを配信した際に自動補完するドメイン名（ドメイン名とする）
myorigin = $mydomain
# メールを受信するネットワークインターフェイス（すべて）
inet_interfaces = all
# 許可するネットワークレンジ
mynetworks = 127.0.0.0/8

# Postfix の sendmail コマンドのフルパス
sendmail_path = /usr/sbin/sendmail
# Postfix の newaliases コマンドのフルパス
newaliases_path = /usr/bin/newaliases
# Postfix の mailq コマンドのフルパス
mailq_path = /usr/bin/mailq
# メール送信とキュー管理のためのグループ
setgid_group = postdrop
# Postfix の HTML ドキュメントの場所
html_directory = no
# Postfix の マニュアルの場所
manpage_directory = /usr/share/man
# Postfix の サンプル設定ファイルの場所
sample_directory = /etc/postfix
# Postfix の README ファイルの場所
readme_directory = /usr/share/doc/postfix

# ここから設定ファイルの下部に追記
# 転送先の上位メールサーバ（今回は Office365）
relayhost = [smtp.office365.com]:587
# SMTP-AUTH を有効
smtp_sasl_auth_enable = yes
# 転送先のサーバのアドレスと認証するユーザ名とパスワードを記述したファイル
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
# 匿名ログインを許可しない
smtp_sasl_security_options = noanonymous
# 転送先のサーバの SASL メカニズムに対するフィルタ
smtp_sasl_mechanism_filter = LOGIN
# 可能ならば TLS 暗号化
smtp_tls_security_level = may
# CA証明書（ca-certificates）
smtp_tls_CApath = /etc/ssl/certs/ca-certificates.crt
```

* SMTP-AUTH の転送先のサーバのアドレスと認証するユーザ名とパスワードを記述したファイルを作成する

```bash
$ sudo vi /etc/postfix/sasl_passwd
```

* 転送先のサーバのアドレスと認証するユーザ名とパスワードを記述する

```text
[smtp.office365.com]:587 ユーザー名（メールアドレス）:パスワード
```

* ファイルのパーミッションを変更する

```bash
$ sudo chmod 600 /etc/postfix/sasl_passwd
```

* Postfix が参照するデータベースファイルに反映する

```bash
$ sudo postmap hash:/etc/postfix/sasl_passwd
```

* Postfix を再起動する

```bash
$ sudo service postfix restart
```

* ログを確認する

```bash
$ tail -f /var/log/mail.log
$ cat /var/log/mail.err
```

* 参考）ログが出力されない場合、rsyslog のプロセスや設定や状態を確認する

```bash
# プロセスの確認
$ ps aux|grep rsyslog|grep -v grep
# 設定の確認
$ sudo /usr/sbin/rsyslogd -n -d
$ cat /etc/rsyslog.d/50-default.conf
# 状態の確認
$ sudo /etc/init.d/rsyslog status
# プロセスの開始
$ sudo /etc/init.d/rsyslog start
```

* 参考）このようなエラーがログに記録されている場合はメールエイリアスのデータベースを再構築してみる

  > error: open database /etc/aliases.db: No such file or directory

```bash
# Postfixを停止してメールエイリアスのデータベースを再構築して開始
$ sudo service postfix stop
$ sudo newaliases
$ sudo service postfix start
# メールエイリアスのデータベースの存在確認
$ ls /etc/aliases.db
```

## 送信したメールを中継して Office365 へ転送する確認

* telnet を利用してメール中継を確認する

```bash
$ telnet hostname.example.jp 25
```

* 確認のためのメールを送信する

```telnet
Trying 127.0.1.1...
Connected to hostname.example.jp.
Escape character is '^]'.
220 hostname.example.jp ESMTP Postfix (Ubuntu)
(入力) HELO hostname.example.jp
250 hostname.example.jp
(入力) MAIL From: 送信元メールアドレス
250 2.1.0 Ok
(入力) RCPT To: 送信先メールアドレス
250 2.1.5 Ok
(入力) DATA
354 End data with <CR><LF>.<CR><LF>
(入力) This is test mail.
(入力) .
250 2.0.0 Ok: queued as EAD291C000000084498
(入力) quit
221 2.0.0 Bye
Connection closed by foreign host.
```

* Office365 のアカウントを設定している MUA（ Outlook ）で、中継したメールが受信できることを確認する

* メールが受信できない場合はログを確認してみる

```bash
$ tail -f /var/log/mail.log
$ cat /var/log/mail.err
```

## Postfix の content_filter を利用して Python プログラムでフィルタ

* Python3 と pip をインストールする

```bash
$ sudo apt install python3
$ sudo apt install python3-pip
```

* Python のバージョンを確認する

```bash
$ python3 -V
$ pip3 -V
```

* プログラムを配置するディレクトリとファイルを作成する

```bash
$ cd ~
$ mkdir postfix
$ cd postfix
$ touch transfer_mail.py
$ chmod 755 transfer_mail.py
```

* プログラムを書く

```python
#!/usr/bin/python3

import sys
import os
import email
import email.parser
from os.path import join, dirname
from subprocess import Popen, PIPE

# 標準入力から取得
mime_str = sys.stdin.read()
# mime_str = open(sys.argv[4]).read()
# Eメールとして解析
message = email.parser.Parser().parsestr(mime_str)
# メールの内容を吐き出すファイル
filepath = join(dirname(__file__), 'result.txt')
# メールの内容をファイルに吐き出す
with open(filepath, mode='w') as f:
    # パラメータ
    for arg in sys.argv:
        f.write(f"{arg}\n")
    # メールヘッダ
    for key in message.keys():
        value = message.get(key)
        f.write(f"{key} : {value}\n")
    # メール本文
    payload = message.get_payload()
    f.write(payload)
# 送信元、送信先、件名を設定
message["From"] = sys.argv[1]
message["To"] = sys.argv[2]
message["Subject"] = "Test mail !!!"
# メールを転送
command = ["/usr/sbin/sendmail", "-t", "-oi"]
stdout = ''
stderr = ''
retval = 0
process = Popen(command, stdin=PIPE)
(stdout, stderr) = process.communicate(message.as_string().encode())
retval = process.wait()
if retval == 0:
    sys.exit(0)
else:
    sys.exit(1)
```

* Postfix の master.cf を編集する

```bash
$ sudo vi /etc/postfix/master.cf
```

* コメントを外したり設定を変更した個所のみ抜粋

```bash
# content_filter を指定する
smtp      inet  n       -       n       -       -       smtpd -o content_filter=filter:dummy

# content_filter を定義する（実行ユーザー、実行するプログラム、送信者、受信者、サイズ）
filter    unix  -       n       n       -       10      pipe
  flags=Rq user=ユーザー argv=/home/ユーザー/postfix/transfer_mail.py ${sender} ${recipient} ${size}
```

* Postfix を再起動する

```bash
$ sudo service postfix restart
```

* ログを確認する

```bash
$ tail -f /var/log/mail.log
$ cat /var/log/mail.err
```

## 送信したメールを中継して Python で加工して Office365 へ転送する確認

* telnet を利用してメール中継を確認する

```bash
$ telnet hostname.example.jp 25
```

* 確認のためのメールを送信する

```terminal
Trying 127.0.1.1...
Connected to hostname.example.jp.
Escape character is '^]'.
220 hostname.example.jp ESMTP Postfix (Ubuntu)
(入力) HELO hostname.example.jp
250 hostname.example.jp
(入力) MAIL From: 送信元メールアドレス
250 2.1.0 Ok
(入力) RCPT To: 送信先メールアドレス
250 2.1.5 Ok
(入力) DATA
354 End data with <CR><LF>.<CR><LF>
(入力) This is test mail.
(入力) .
250 2.0.0 Ok: queued as EAD291C000000084498
(入力) quit
221 2.0.0 Bye
Connection closed by foreign host.
```

* Office365 のアカウントを設定している MUA（ Outlook ）で、中継したメールが受信できたことを確認する

* テキストファイルが出力されたことを確認する

```bash
$ cat ~/postfix/result.txt
```

* テキストファイルの内容

```text
/home/ユーザー/postfix/transfer_mail.py ← 実行したプログラム
送信元メールアドレス ← フィルタのパラメータ1
送信先メールアドレス ← フィルタのパラメータ2
サイズ              ← フィルタのパラメータ3
Return-Path : <中継メールアドレス>
Received : from hostname.example.jp (localhost [127.0.0.1])
	by hostname.example.jp (Postfix) with SMTP id 923AE9E00000003FBF1
	for <送信先メールアドレス>; Fri,  9 Oct 2020 23:55:00 +0900 (JST)
Message-Id : <20201009113146.923AE9E00000003FBF1@hostname.example.jp>
Date : Fri, 10 Oct 2020 23:55:00 +0900 (JST)
From : 送信元メールアドレス
This is test mail.（本文）
```

* ログを確認する

```bash
$ tail -f /var/log/mail.log
```

```text
Oct 10 23:55:00 hostname postfix/qmgr[10858]: 923AE9E00000003FBF1: from=<送信元メールアドレス>, size=362, nrcpt=1 (queue active)
Oct 10 23:55:00 hostname postfix/pipe[10951]: 923AE9E00000003FBF1: to=<送信先メールアドレス>, relay=filter, delay=591, delays=591/0.01/0/0.08, dsn=2.0.0, status=sent (delivered via filter service)
Oct 10 23:55:00 hostname postfix/qmgr[10858]: 923AE9E00000003FBF1: removed
Oct 10 23:55:00 hostname postfix/pickup[10857]: 2047A9F00000003FBF1: uid=1000 from=<ユーザー>
Oct 10 23:55:00 hostname postfix/cleanup[10955]: 2047A9F00000003FBF1: message-id=<20201009113146.923AE9E00000003FBF1@hostname.example.jp>
Oct 10 23:55:00 hostname postfix/qmgr[10858]: 2047A9F00000003FBF1: from=<ユーザー@hostname.example.jp>, size=567, nrcpt=1 (queue active)
Oct 10 23:55:00 hostname postfix/smtp[10957]: warning: vstream_tweak_tcp: getsockopt TCP_MAXSEG: Protocol not available
Oct 10 23:55:00 hostname postfix/smtp[10957]: 2047A9F00000003FBF1: to=<送信先メールアドレス>, relay=smtp.office365.com[XXX.XXX.XXX.XXX]:587, delay=1.6, delays=0.03/0.02/1.1/0.4, dsn=2.0.0, status=sent (250 2.0.0 OK <20201009113146.923AE9E00000003FBF1@hostname.example.jp> [Hostname=XXXXXXXXXXXXX.xxxxxxxx.xxxx.outlook.com])
Oct 10 23:55:00 hostname postfix/qmgr[10858]: 2047A9F00000003FBF1: removed
```

## おわりに

ログの vstream_tweak_tcp の警告が気になりますが、送信したメールを中継して加工したうえで Office365 へ転送することができました。
他サービスとの連携や開発時の調査などに利用できそうです。