hosting mail lolipop heteml ムームードメイン MRE

メール送信を Postfix の Policy Delegation で制御する

hosting mail lolipop heteml ムームードメイン MRE

こんにちは、福岡の街散策にハマっている坂尾です。路地にある小さなお寺や神社を見つけたらテンションが上がります。今回の記事が良ければ、皆さんのお気に入りの素敵な街を教えてください。

ホスティング事業部の MRE(Messaging Reliability Engineering *ペパボの造語です)というチームで、 SRE のような取り組みを、DNS やメールなどのメッセージングサービスに対して実施しています。

前回は、原口の方から ホスティングサービスのメールシステムの概要について紹介 していただきました。

メールシステムのSMTPサーバー

弊社のメールシステムでは、大量のメール送信を防ぐために、一定期間における送信数の上限を設けています。メール送信数を制限するためには、以下のような機能を使って制御することが必要です。

  • Milter: Sendmail で開発された拡張機能で、フィルタリングやポリシーを追加できる
  • SMTP Access Policy Delegation: Postfixの機能で、外部サーバーへポリシー委譲できる

今回は、SMTP Access Policy Delegationを使って送信数制限を実装しているので、ご紹介いたします。

メール送信構成図

Postfix の Policy Delegation

Postfix には Policy Delegation という機能があります。これはPostfixがSMTPプロトコルの各段階(MAIL FROM、RCPT TOなど)にて、外部サーバーにポリシーの決定を委譲できる仕組みです。

このPolicy Delegationを用いて、メール送信が行われる際に、Postfixから送信数制限用サーバーにポリシー委譲することによって、送信数制限の仕組みを実現しています。

Postfixとポリシーサーバーは、TCPやUNIXドメインソケットでやり取りしてます。まずPostfixからポリシーサーバーへ以下のようなname=value形式でデータを送ります。

request=smtpd_access_policy
protocol_state=DATA
recipient_count=1
sasl_username=test@example.com
[empty line]

データを受け取ったポリシーサーバーは access(5) にある action をPostfixに返します。以下にactionの一部を抜粋します。

  • DEFER_IF_REJECT optional text…
    • 以降の制限で結果が REJECT アクションになる場合、リクエストを遅延します。これは一時的な問題でホワイトリスト機能が失敗したときに便利です。
  • DUNNO
    • 結果を無視して、以降の制限を行えるようにします。以降の制限を行うために OK の代わりに使用されます。
  • PREPEND headername: headervalue
    • メッセージの前に指定されたメッセージヘッダを付加します。このアクシ ョンが複数回使われると、最初に付加されたヘッダは2番目などのヘッダの前に現れます

今回は、送信数の上限に達した場合は DEFER_IF_REJECT を返し、達していない場合は DUNNO を返すようにしました。

Policy Server

送信数制限に使われるポリシーサーバーはGoで作成しました。Go が選定された理由はチームの技術によるものです。作りはシンプルで、TCPサーバーを立ち上げてPostfixから受け取ったデータを元に送信制限を行うか否かのレスポンスをPostfixに返しています。(公開できるように一部変更しております)

func main() {
...
	ln, err := net.Listen("tcp", config.Addr.Listen)
	if err != nil {
		logger.Fatal(err)
	}
...
	for {
		conn, err := ln.Accept()
		if err != nil {
			logger.Fatal(err)
		}
		go func(conn net.Conn) {
			defer conn.Close()
			// データを受け取ってactionを返す.
			// 制限時: action=DEFER_IF_REJECT Service temporarily unavailable
			// 送信時: action=DUNNO
			action := SmtpdAccessPolicy(conn)
			// actionをレスポンスに書いてPostfixに渡す
			// データの終わり(EOM)を示すために最後の空行を入れる
			conn.Write([]byte(fmt.Sprintf("action=%s\n\n", action)))
		}(conn)
	}

Postfix 設定

上記のポリシーサーバーに問い合わせるための設定は、まず smtpd_*_restrictions でフックするSMTPコマンドを指定します。そして check_policy_service の後にポリシーサーバーを指定します。指定できる設定は Postfixの公式ドキュメント をご確認ください。

今回は smtpd_data_restrictions を指定して、フックするタイミングをDATAコマンドの実行時にします。

# /etc/postfix/main.cf
smtpd_data_restrictions = check_policy_service inet:127.0.0.1:11598

この設定を入れたらPostfixをリロードして設定を反映させます。

systemctl reload postfix

動作確認

では、ポリシーサーバー側で送信数制限に達したアドレスがメールを送信しようとした時どうなるのでしょうか。送信数制限されたメールログを見てみましょう。

# /var/log/maillog
May 23 04:09:20 mail-proxy postfix/smtpd[19258]: NOQUEUE: reject: DATA from unknown[127.0.0.1]: 450 4.7.1 <DATA>: Data command rejected:

まず NOQUEUE とあるのでキューに追加されていないことがわかります。そして reject とあるので、メールが拒否されています。これは、送信数制限に達した場合ポリシーサーバーは DEFER_IF_REJECT をPostfixに渡すようにしたからです。

450 4.7.1 は一時的なエラーを表しているエラーコードです。最後に Data command rejected となっているので、このメールは DATAコマンドで失敗していることがわかります。

まとめ

私たちがPostfixのPolicy Delegationを用いてメール送信の送信数制限を実装した方法について紹介いたしました。送信数制限以外にもGraylistingの導入など、Policy Delegationを使って Postfixの動作を柔軟にカスタマイズできます。ぜひお試しください。