現行メールサーバ宛に届いたメールのコピーを、次期メールサーバに転送する

## 前提

メールシステム移転のため、現行メールサーバに届いたメールのコピーを、次期メールサーバにも転送しなければならない（並行配送しなければならない）ことになりました。

まず、次期メールサーバでは `alice@example.jp` だけでなく `alice@migration.example.jp` でもメールを受け取るように設定しました。後は、現行メールサーバに届いた、`alice@example.jp` 宛のメールのコピーを `alice@migration.example.jp` にも送信するように設定すれば良いだけです。

## 案①転送用 LDAP 属性を新設する

本学のメールシステムでは、各利用者のメール転送先アドレスは ~/.forward ではなく LDAP で管理されています。

```conf:/etc/postfix/main.cf
myhostname = old-mail-server.example.jp
mydomain = example.jp
mydestination = $myhostname, $mydomain
alias_maps =
	hash:/etc/postfix/aliases
	ldap:/etc/postfix/forward.cf
```

```conf:/etc/postfix/forward.cf
server_host=ldap.example.jp
server_port=389
timeout=20
search_base=ou=users,dc=example,dc=jp
scope=one
query_filter=(uid=%u)
result_attribute=examplePersonMailForward
```

したがって、各ユーザに LDAP 属性を追加すれば、まったく難しいことはなかったんです。例えば、次のように属性を追加して、

```ldif:alice.ldif
dn: uid=alice,ou=users,dc=example,dc=jp
examplePersonMailForward: alice-mobile@docomo.ne.jp
examplePersonMailMigrationAddress: alice@migration.example.jp
```

以下のように、LDAP の検索で2つの属性の値を両方返すように設定する。

```conf:/etc/postfix/forward.cf
server_host=ldap.example.jp
server_port=389
timeout=20
search_base=ou=users,dc=example,dc=jp
scope=one
query_filter=(uid=%u)
result_attribute=examplePersonMailForward, examplePersonMailMigrationAddress
```

ところが、どんな場合でも、やむを得ない事情というのは存在するものです。例えば、メールサーバと LDAP サーバを管轄している業者が違うとか、LDAP サーバを含む認証基盤システムについても設定変更が予定されていて LDAP サーバの急な設定変更（上記はスキーマの変更を必要としますから）が難しいとか、メールシステム移転までの残日数が少なくて LDAP サーバの設定変更が待てないとか…

## 案②canonical_maps と virtual_alias_maps を使う

というわけで、代替案です。qmgr(8) および local(8) の段階で並行配送できないならば、その前段階である cleanup(8) の段階で並行配送するしかありません。しかし、動作テストが大変になるので、mydomain パラメータや mydestination パラメータは変更したくありません。というわけで、以下のような案が考えられます。

```
      ↓
+-----------+
|   smtpd   |
+-----------+
      ↓
+-----------+ ①canonical_maps を使って、alice@example.jp 宛メールを一旦、alice@migration-forward.example.jp 宛に書き換える。
|  cleanup  | ②virtual_alias_maps を使って、alice@migration-forward.example.jp 宛のメールを
+-----------+   alice@example.jp 宛と alice@migration.example.jp 宛に書き換える。
      ↓
+-----------+
|   qmgr    |
+-----------+
      ↓
```

しかし、実際には、現行メールサーバでは既に virtual_alias_maps パラメータにかなり面倒な設定が作り込まれているために、上記のように単純に変更することはできませんでした…


## 現実：content_filter と canonical_maps と virtual_alias_maps を使う

というわけで、現実の経路は、以下のようになります。scansmtpd と scancleanup は、この設定のために新設したサービス名です。

```
      ↓
+-----------+
|   smtpd   |
+-----------+
      ↓
+-----------+
|  cleanup  | canonical_maps と virtual_alias_maps は既存システムが利用している
+-----------+
      ↓ content_filter パラメータを使って、全てのメールを localhost:10025 に転送
+-----------+
| scansmtpd | localhost:10025 で待受
+-----------+
      ↓
+-----------+ ①canonical_maps を使って、alice@example.jp 宛メールを一旦、alice@migration-forward.example.jp 宛に書き換える。
|scancleanup| ②virtual_alias_maps を使って、alice@migration-forward.example.jp 宛のメールを
+-----------+   alice@example.jp 宛と alice@migration.example.jp 宛に書き換える。
      ↓
+-----------+
|   qmgr    |
+-----------+
      ↓
```

設定としては、[高度なコンテンツフィルタを使う設定](http://www.postfix-jp.info/trans-2.3/jhtml/FILTER_README.html#advanced_filter)を少しアレンジした形になります。まず、必要なサービスを作ります。

```conf:/etc/postfix/master.cf
scan		unix	-	-	n	-	64	smtp
  -o syslog_name=postfix/scan
  -o smtp_send_xforward_command=yes
  -o smtp_enforce_tls=no
10025		inet	n	-	n	-	64	smtpd
  -o syslog_name=postfix/scan
  -o content_filter=
  -o smtpd_helo_restrictions=
  -o smtpd_client_restrictions=
  -o smtpd_sender_restrictions=
  -o smtpd_recipient_restrictions=permit_mynetworks,reject
  -o mynetworks=127.0.0.0/8
  -o smtpd_authorized_xforward_hosts=127.0.0.0/8
  -o cleanup_service_name=scancleanup
scancleanup	unix	n	-	-	-	0	cleanup
  -o syslog_name=postfix/scan
  -o canonical_classes=envelope_recipient
  -o canonical_maps=hash:/etc/postfix/migration_rewrite
  -o virtual_alias_domains=migration-forward.example.jp
  -o virtual_alias_maps=tcp:localhost:10003
```

scan は localhost:10025 で待ち受けている smtpd に配送するための特別な配送経路で、content_filter パラメータに指定します。

```conf:/etc/postfix/main.cf
content_filter = scan:localhost:10025
```

localhost:10025 で待ち受けている smtpd（図では scansmtpd と記載）は、並行配送を行うための特別な cleanup プロセス（図では scancleanup と記載）を呼び出すための特別な smtpd です。そのために、`/etc/postfix/master.cf` では、明示的に cleanup_service_name を書き換えています。

scancleanup が実際の並行配送のためのアドレス書き換えを行います。

```conf:/etc/postfix/migration_rewrite
@example.jp @migration-forward.example.jp
```

というようなファイルを用意して、

```shell-session
$ sudo postmap hash:/etc/postfix/migration_rewrite
```

データベースを作成しておきます。scancleanup 配送はまず、canonical_classes と canonical_maps の指定にしたがって、配送先メールアドレスの `alice@example.jp` を `alice@migration-forward.example.jp` に変換します。

その次に、virtual_alias_domains と virtual_alias_maps の指定により、実際に並行配送したいアドレスに変換します。scancleanup 配送に関する設定は、以下の通り（再掲）です。

```conf:/etc/postfix/master.cf
scancleanup	unix	n	-	-	-	0	cleanup
  -o syslog_name=postfix/scan
  -o canonical_classes=envelope_recipient
  -o canonical_maps=hash:/etc/postfix/migration_rewrite
  -o virtual_alias_domains=migration-forward.example.jp
  -o virtual_alias_maps=tcp:localhost:10003
```

ここで、virtual_alias_domains が `migration-forward.example.jp` となっていることがポイントです。このドメインに属するアドレス、例えば `alice@migration-forward.example.jp` 宛のメールの宛先は、TCP アドレス変換サーバに問い合わせを行うようになっています。この部分についての詳細は、「[任意のスクリプトを使ってアドレス変換を行う](https://qiita.com/tsuchm/items/a0b7a0b6615d283f9813)」を参照してください。