任意のスクリプトを使ってアドレス変換を行う

## 概要

3000人程度の利用者に対するメールシステムを、かれこれ12年間、オンプレミスで運用してきたのですけれど、ついに G Suite に移転することになりました。その間に得たノウハウを、このままロストテクノロジーにするのも惜しいので、過去の作業ログの中から、ある程度汎用性がありそうなトピックを取り出して、公開しようと思います。

あるアドレス宛のメールを別のアドレス宛に読み替える操作は、システム更新やドメイン移転など各種の状況で頻繁に必要になる操作です。多くの場合は、読み替えに必要な情報を LDAP サーバに格納しておき、[LDAP サーバを参照する設定](http://www.postfix-jp.info/trans-2.3/jhtml/LDAP_README.html)を行えば良いのですが、大量の情報を読み替えなければならない場合には、LDAP サーバに複数の互いに整合した情報を格納しなければならず、単に LDAP サーバに格納するだけでは実現が難しくなることがあります。

そのような場合、任意のスクリプトを使ってアドレス変換を行うことができると便利です。本稿では、[tcp_table](http://www.postfix-jp.info/trans-2.3/jhtml/tcp_table.5.html) 機能を利用して、任意のスクリプトを使ってアドレス変換を行う方法を示します。

## 設定

本稿では、alice@old.example.net を ecila@new.example.net に読み替えなければならない（転送しなければならない）場合を考えます。

ローカルパートの文字列を、文字単位で逆順に並べ替えなければならないところがポイントです。もしも単純に alice@old.example.net を alice@new.example.net に読み替えれば良いだけならば、もっと簡単に `virtual_alias_domains` オプションと `virtual_alias_maps` オプションを利用して、以下のように書くこともできます。

```conf:/etc/postfix/main.cf
virtual_alias_domains = old.example.net
virtual_alias_maps = hash:/etc/postfix/rewrite
```

```conf:/etc/postfix/rewrite
@old.example.net @new.example.net
```

以下、本稿の設定は3段階からなります。

 * 読み替え用スクリプトを用意する
 * 読み替え用スクリプトを TCP で待ち受けるサーバにする
 * 読み替え用サーバを使って、アドレスを読み替える

### 読み替え用スクリプトを用意する

以下のような、読み替え用スクリプト `/usr/loca/bin/sample-rewrite.pl` を用意します。

```perl:/usr/local/bin/sample-rewrite.pl
#!/usr/bin/perl

use FileHandle;
use strict;
use constant OLD_DOMAIN => 'old.example.net';
use constant NEW_DOMAIN => 'new.example.net';

&main();

sub main {
    autoflush STDIN;
    autoflush STDOUT;
    autoflush STDERR;
    while( my $query = <STDIN> ){
        &rewrite( $query );
    }
    exit 0;
}

sub rewrite {
    my( $buf ) = @_;
    $buf = &hex_decode( $buf );
    if( $buf =~ s/\Aget // and $buf =~ s/\s+\Z// ){
        my( $local, $domain ) = ( $buf =~ m/\A([^\@]+)\@(.*)\Z/ );
        if( $domain ne &OLD_DOMAIN ){
            printf "500 No target domain: address=%s\n", $buf;
            return;
        }
        printf "200 %s\n", &hex_encode( sprintf('%s@%s', join('',reverse(split(//,$local))), &NEW_DOMAIN ) );
    } elsif( $buf =~ /\A\s*\Z/ ){
        exit 0;
    } else {
        print "500 Not implemented\n";
    }
}

sub hex_decode {
    $_[0] =~ s/%([a-fA-F0-9]{2})/chr(hex($1))/eg;
    $_[0];
}

sub hex_encode {
    $_[0] =~ s/(\W)/'%'.sprintf('%2.2x',ord($1))/eg;
    $_[0];
}
```

このスクリプトは、標準入力に `get alice@old.example.net` が入力されると、新しいドメインのアドレスを返します。

```shell-session
$ echo "get alice@old.example.net" | /usr/local/bin/sample-rewrite.pl
200 ecila%40new%2eexample%2enet
```

本稿のポイントは、ここで示すような **標準入出力を使うだけの簡単なスクリプト** を読み替え用に使うことが出来る、という点です。

### 読み替え用スクリプトを TCP で待ち受けるサーバにする

前述の **標準入出力を使う** 読み替え用スクリプトを、Postfix の機能を使って、TCP で待ち受けるサーバにします。以下の設定を `/etc/postfix/master.cf` に追記します。

```conf:/etc/postfix/master.cf
127.0.0.1:10001	inet	n	n	n	-	600	spawn
  user=nobody argv=/usr/bin/perl /usr/local/bin/sample-rewrite.pl
```

これで、前述の読み替え用スクリプトが localhost の 10001 番ポートで待ち受けるサーバに早変わりします。この読み替え用サーバをテストするには、以下のように `postmap` コマンドを使って下さい。

```shell-session
$ postmap -q alice@old.example.net tcp:localhost:10001
ecila@new.example.net
```

### 読み替え用サーバを使って、アドレスを読み替える設定

以下のように、`/etc/postfix/main.cf` に設定して下さい。

```conf:/etc/postfix/main.cf
virtual_alias_domains = old.example.net
virtual_alias_maps = tcp:localhost:10001
```

これで、alice@old.example.net 宛のメールは ecila@new.example.net 宛に読み替えられる（転送される）ようになります。