テスト用に、外にメールを送らないメールサーバをつくろう

あなたのアプリケーションでは、電子メールを使っていますか?
Yesであれば、どうやってデバッグしていますか?


電子メール送信は rollback できない処理の一つだと思います。
間違ってテストのデータをお客さんに送信してしまった!! って日にはひたすら謝るしかありません。。。


もちろん、プログラム側での工夫だったり、あらかじめメールアドレスを塗りつぶしたデータを使うなどの予防策を講じれば、ご送信をある程度は防げますが、、、、もっとシステムチックな誤送信対策はないでしょうか。


と、いうことで、 prostfix と sendmail で外部にメールを送信しない smtp サーバをつくろうってわけです。
すべてのメールアドレスをたたき落としてしまうと、検証が非常に難しくなるため、ここでは、 自社とパートナーのドメインだけは通過させ、それ以外は叩き落すというようなルールにしてみたいと思います。

要件

自社 (@my.example.com)とパートナー(@partner.example.com) のドメインのメールアドレスにはメールを送信するが、それ以外のすべてのメールアドレスのメールは送信しない。
テストユーザは、 とりあえず @user.example.com ドメインからメールを送信する。

postfix 方法その1 smtpd_sender_restrictionsを使う場合

smtpd_sender_restrictions を使うと、 RCPT TO の段階の処理で許可したり、拒否ったりできます。

1.
#末尾に追加
vi /etc/postfix/main.cf
----------------------------------------------------------------------------
smtpd_sender_restrictions = check_recipient_access regexp:/etc/postfix/check_recipient_access, reject
----------------------------------------------------------------------------

2.
#新規作成
vi /etc/postfix/check_recipient_access
----------------------------------------------------------------------------
/@my.example.com/ OK
/@partner.example.com/ OK
----------------------------------------------------------------------------

3.
#postfixを再起動する
/etc/init.d/postfix restart

4.
#テスト
#メールアドレスはサンプルなので書き換えてから試してね

#メールが届かないことを確認する.
#@user.example.com へのメールは許可されていないので、
#途中の RCPT TO でエラーになり、メールが送信できなくなる。
telnet 127.0.0.1 25
---------------------------------------------------------------------------
HELO 127.0.0.1
MAIL FROM: <my@my.example.com>
RCPT TO: <user1@user.example.com>
DATA
Subject: HogeHoge
From: <my@my.example.com>
To: <user1@user.example.com>

this is test.
.
QUIT
----------------------------------------------------------------------------




#許可されたドメインへはメールが飛ぶことを確認する.
#@my.example.comへの送信は許可されているので送信できる.
telnet 127.0.0.1 25
---------------------------------------------------------------------------
HELO 127.0.0.1
MAIL FROM: <my@my.example.com>
RCPT TO: <my_my@my.example.com>
DATA
Subject: HogeHoge
From: <my@my.example.com>
To: <my_my@my.example.com>

this is test.
.
QUIT
----------------------------------------------------------------------------

postfix 方法その2 header_checksを使う場合

header_checks を使うと、メールヘッダの部分を見て、もっと細かい制御ができます。
RCPT TO「ではない」ことに注意してください。
また、あとで書きますが、超変なクセがあり、はまると地獄を見ます。
#例では、拒否るのに REJECT していますが、DISCARDでもいいかも。

1.
#末尾に追加
vi /etc/postfix/main.cf
----------------------------------------------------------------------------
header_checks = regexp:/etc/postfix/header_checks
----------------------------------------------------------------------------

2.
#新規作成
vi /etc/postfix/header_checks
----------------------------------------------------------------------------
/^To:.*@my.example.com/ OK
/^To:.*@partner.example.com/ OK
/^To:.*/ REJECT
----------------------------------------------------------------------------

3.
#postfixを再起動する
/etc/init.d/postfix restart

4.
#テスト
#メールアドレスはサンプルなので書き換えてから試してね

#メールが届かないことを確認する.
#@user.example.com へのメールは許可されていないので、送信されない。
telnet 127.0.0.1 25
---------------------------------------------------------------------------
HELO 127.0.0.1
MAIL FROM: <my@my.example.com>
RCPT TO: <user1@user.example.com>
DATA
Subject: HogeHoge
From: <my@my.example.com>
To: <user1@user.example.com>

this is test.
.
QUIT
----------------------------------------------------------------------------


#許可されたドメインへはメールが飛ぶことを確認する.
#@my.example.comへの送信は許可されているので送信できる.
telnet 127.0.0.1 25
---------------------------------------------------------------------------
HELO 127.0.0.1
MAIL FROM: <my@my.example.com>
RCPT TO: <my_my@my.example.com>
DATA
Subject: HogeHoge
From: <my@my.example.com>
To: <my_my@my.example.com>

this is test.
.
QUIT
----------------------------------------------------------------------------

header_checks の はまりどころ

header_checks はクソ野郎なので、仕様を理解しないとトンでもいない落とし穴にハマって泣かされます。


よくある間違いの例

----------------------------------------------------------------------------
/@my.example.com/ OK
/@partner.example.com/ OK
/./ REJECT
----------------------------------------------------------------------------

これは正しく動きません。
一見すると、どれにもマッチしなかったら、 reject しろというようにも見ます。
しかし、騙されてはいけません。


ソースを追いかけたわけぢゃないんですけど、多分こんな感じの擬似コードな処理になっているはずです。

//メールヘッダーを位置行づつ読み込む
foreach($headers as $line)
{
   //ユーザの定義ファイルを位置行づつ評価
   foreach(/etc/postfix/header_checks as $regex)
   {
       //マッチするの?
       r = match($regex , $line);
   }
}

つまり、位置行毎に /etc/postfix/header_checks ファイルが上から走査されるわけです。
で、メールヘッダには、 To: の行もあれば From: の行もありますよね。
もし、 From: の行がこの /etc/postfix/header_checks に入ってきたらどうなりますか?
もちろん、どこにもマッチせずに、 reject されてしまうのです。

#From: <user1@user.example.com> が入ってきたら、 reject にマッチしてしまう。。。

----------------------------------------------------------------------------
/@my.example.com/ OK
/@partner.example.com/ OK
/./ REJECT                     ←マッチ
----------------------------------------------------------------------------

あれあれ、OKに先にマッチしている可能性もあのでは?と思うかもしれません。
はい。たしかにそうですね。


ところが、 postfix の OK は非常に弱いのです。
reject は、一度でもマッチしてしまうとそこで即終了で拒否る挙動です。C言語て言えば returen 文です。
OKは、C言語でいう break みたいなものです。このループ処理を抜けて次の処理行くよ、みたいな。

//メールヘッダーを位置行づつ読み込む
foreach($headers as $line)
{
   //ユーザの定義ファイルを位置行づつ評価
   foreach(/etc/postfix/header_checks as $regex)
   {
       r = match($regex , $line);
       if (r == "OK")
       {
          //OKだったら、このチェックは抜ける
          //だけど、外側には 別のループが、、
          break;
       }
       if (r == "REJECT")
       {
          //一つでも REJECTされたら拒否る.
          return REJECT;
       }
   }
}
REJECT optional text...
      メ ッ セ ー ジ全体を拒否します。optional text が指定されていれば
      optional text... を付けて応答し、指定がなければ一般的なエラー メ
      ッセージで応答します。

      注意: このアクションは現在のメッセージの検査をこれ以上おこなわな
      いようにします。また、受信者全てに影響を与えます。

#↓実はOKはこのコマンドと同じ意味です。
DUNNO  このテーブルに検索キーが見つからなかったように見せ、次の入力行を
      検査します。このアクションはテーブル検索を短くするのに使われます
      。

      後方互換性のため、Postfix は OK も受け付けますが、これは (これま
      でもそうでしたが) DUNNO として扱われます。

      この機能は Postfix 2.1 以降で使えます。


http://www.postfix-jp.info/trans-2.2/jhtml/header_checks.5.html より

はい。実にひどい話ですね。
ですが、これが「仕様」なんです。現実は冷てえぇよな。

#なんで、OK の REJECTバージョンをつくらなかったし。
#
#まぁ、基本はスパムメールフィルタで叩き落すことを前提に作っているからなんだろうけどさ。。。
#だから、ブラックリストを簡単に作れるけど、ホワイトリストを作るのは難しい。

rejectではなくて、メールの宛先を変更したい

メールを reject で拒否るとメールが届きません。
まぁ、当たり前です。


しかし、メールが届かないとデバッグがしづらい場合もあります。
たとえば、アンケートフォームみたいなもので、メールアドレスがユニークキーになっているケースなどです。
これもシステムの作りである程度は対処できますが、、、やはり、メールは飛んできてくれたほうが嬉しいですよね。


なので、メールアドレスを書き換えて、特定のメールアドレス(メーリングリスト等)に飛ばすように設定してみましょう。

header_checksを使って、特定のドメイン以外はテスト用のメーリングリストにリダイレクトする
1.
#末尾に追加 これは前回と一緒。前回やっているなら不要。
vi /etc/postfix/main.cf
----------------------------------------------------------------------------
header_checks = regexp:/etc/postfix/header_checks
----------------------------------------------------------------------------

2.
#新規作成
vi /etc/postfix/header_checks
----------------------------------------------------------------------------
/^To:.*@my.example.com/ OK
/^To:.*@partner.example.com/ OK
/^To:.*/ REDIRECT debugmail@my.example.com
----------------------------------------------------------------------------

3.
#postfixを再起動する
/etc/init.d/postfix restart


4.
#テスト
#メールアドレスはサンプルなので書き換えてから試してね


#メールが本来のアドレスに届かずに、debugmail@my.example.com へリダイレクトされることを確認する
#@user.example.com は、debugmail@my.example.com に宛先が変更されて転送される。
#元々の宛先 user1@user.example.com にはメールは送信されない。

telnet 127.0.0.1 25
---------------------------------------------------------------------------
HELO 127.0.0.1
MAIL FROM: <my@my.example.com>
RCPT TO: <user1@user.example.com>
DATA
Subject: HogeHoge
From: <my@my.example.com>
To: <user1@user.example.com>

this is test.
.
QUIT
----------------------------------------------------------------------------


#許可されたドメインへはメールが飛ぶことを確認する.
#@my.example.comへの送信は許可されているので送信できる.
telnet 127.0.0.1 25
---------------------------------------------------------------------------
HELO 127.0.0.1
MAIL FROM: <my@my.example.com>
RCPT TO: <my_my@my.example.com>
DATA
Subject: HogeHoge
From: <my@my.example.com>
To: <my_my@my.example.com>

this is test.
.
QUIT
----------------------------------------------------------------------------

以上です。


そんなに難しくない設定なんですが、 postfix のループ判定の挙動がわからないとめっちゃ苦労します。
ハマって死ぬかと思いました。。。他の人には苦労してほしくない。。。
http://www.postfix-jp.info/trans-2.2/jhtml/header_checks.5.html
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=47649&forum=10

sendmail でメールを送らない設定

手順といっても、このサイトに書いてあることのままなんだけど、、
http://www.ryuzee.com/contents/blog/2695


これについては、sendmailの方がpostfixよりもハマる罠は少ない気がする、、、!?

1.
#ゴミ箱エイリアスを作成する.
#末尾に追加する.
vi /etc/aliases
-------------------------------------
trash: /dev/null
-------------------------------------

2.
#エイリアスを作ったので、認識し直させる
newaliases

3.
#mailertableファイルを作成する
vi /etc/mail/mailertable
-------------------------------------
my.example.com         smtp:my.example.com  
partner.example.com    smtp:partner.example.com
.                      local:trash
-------------------------------------

4.
#データベースに変換する
makemap hash /etc/mail/mailertable </etc/mail/mailertable

5.
#sendmail再起動
/etc/rc.d/init.d/sendmail restart

6.
#テスト
#メールアドレスはサンプルなので書き換えてから試してね

#メールが届かないことを確認する.
#@user.example.com へのメールは許可されていないので、送信されない。
telnet 127.0.0.1 25
---------------------------------------------------------------------------
HELO 127.0.0.1
MAIL FROM: <my@my.example.com>
RCPT TO: <user1@user.example.com>
DATA
Subject: HogeHoge
From: <my@my.example.com>
To: <user1@user.example.com>

this is test.
.
QUIT
----------------------------------------------------------------------------


#許可されたドメインへはメールが飛ぶことを確認する.
#@my.example.comへの送信は許可されているので送信できる.
telnet 127.0.0.1 25
---------------------------------------------------------------------------
HELO 127.0.0.1
MAIL FROM: <my@my.example.com>
RCPT TO: <my_my@my.example.com>
DATA
Subject: HogeHoge
From: <my@my.example.com>
To: <my_my@my.example.com>

this is test.
.
QUIT
----------------------------------------------------------------------------

sendmail を使って、特定のドメイン以外はテスト用のメーリングリストにリダイレクトする
1.
#メーリングリスト用エイリアスを作成する. 
#末尾に追加する.
vi /etc/aliases
-------------------------------------
debugmail: debugmail@my.example.com
-------------------------------------

2.
#エイリアスを作ったので、認識し直させる 
newaliases

3.
#mailertableファイルを作成する
vi /etc/mail/mailertable
-------------------------------------
my.example.com         smtp:my.example.com 
partner.example.com    smtp:partner.example.com
.                      local:debugmail
-------------------------------------

4.
#データベースに変換する
makemap hash /etc/mail/mailertable </etc/mail/mailertable

5.
#sendmail再起動
/etc/rc.d/init.d/sendmail restart

6.
#テスト

#メールが本来のアドレスに届かずに、debugmail@my.example.com へリダイレクトされることを確認する
#@user.example.com は、debugmail@my.example.com に宛先が変更されて転送される。
#元々の宛先 user1@user.example.com にはメールは送信されない。

telnet 127.0.0.1 25
---------------------------------------------------------------------------
HELO 127.0.0.1
MAIL FROM: <my@my.example.com>
RCPT TO: <user1@user.example.com>
DATA
Subject: HogeHoge
From: <my@my.example.com>
To: <user1@user.example.com>

this is test.
.
QUIT
----------------------------------------------------------------------------


#許可されたドメインへはメールが飛ぶことを確認する.
#@my.example.comへの送信は許可されているので送信できる.
telnet 127.0.0.1 25
---------------------------------------------------------------------------
HELO 127.0.0.1
MAIL FROM: <my@my.example.com>
RCPT TO: <my_my@my.example.com>
DATA
Subject: HogeHoge
From: <my@my.example.com>
To: <my_my@my.example.com>

this is test.
.
QUIT
----------------------------------------------------------------------------


以上?

まとめ

開発、ステージング(QC/チェック/確認サーバ)でメールを送信しない smtp サーバの環境すると、オペレーションミスによるリスクを抑えることができます。
だけど、googleで探してもあんまりやり方は出てこないけど、、、みんなどうやってるの?


MILTER とかで華麗に処理しているの?
それとも、mfilter / Barracuda とか、こーゆーサーバ製品使っているの?
プログラムやシステム側で誤送信を防止するロジックをつくりこんでいるの?
まさか根性なんてことはないよねwww