SMTP で使われる Sender Policy Framework (SPF) のマクロを使う

はじめに

こんばんは、気づけば 20 歳の誕生日が9日後に迫っていた yosida95 です。 ご存知のように、私は商用 SMTP サーバーをフルスクラッチして、現在もその運用を行っています。

Gehirn Infrastructure Services の Public Preview 開始によせて

この記事では、その経験から Sender Policy Framework (SPF) の verifier を書いている過程で知ったマクロ機能をご紹介します。

SPF とは

目的

メールサーバーを運用したり、あるいは Google Apps や Amazon SES などを利用している方などはご存知だと思いますが、 SPF とは ドメインの所有者が、そのドメインからのメールを送信するメールサーバーを宣言する するための仕組みです。 この宣言を行うことで、メールを受信したメールサーバーは、受け取ったメールがドメイン所有者から送信元として認証を受けたメールサーバーから送られたものであるかどうかを確認できます。 もし確認できない場合は、送信者を偽ったスパムメールである可能性が高いと判断して、リジェクトすることができます。

仕組み

送信元メールサーバーの宣言はメール送信者が DNS に、自身のドメインの TXT レコードを追加することで行います。 メールを受信したメールサーバーは、送信者が SMTP の MAIL FROM で名乗ったメールアドレス (envelope from) のドメインの TXT レコードを参照し、その記述内容によって送信元メールサーバーが認証を受けているかどうかを確認します。 メッセージの From ヘッダー、あるいは Sender ヘッダーで宣言されているメールアドレスと envelope from は必ずしも一致しない事に留意が必要です。

記述の方法

SPF の記述は、4つの演算子と8つのメカニズムを組み合わせたディレクティブを並べることによって行います。 メカニズムと演算子はそれぞれ、ディレクティブがマッチする条件と、マッチした場合の送信者の評価結果を表します。

各メカニズムと各演算子の説明は以下で行いますが、例えば +ip4:192.0.2.100 というディレクティブがあった場合、送信者の IP アドレスが 192.0.2.100 であればドメイン所有者から認証を受けた正当な送信者して扱います。

ディレクティブは左から右へと評価され、評価の途中でも一致するメカニズムが見つかった場合にはそこで評価が終了します。

演算子

表1. SPF の演算子
演算子 意味
+ 認証を受けた送信者として扱う (pass)
~ 認証を受けた送信者として扱わない (softfail)
認証を受けた送信者として扱わない (fail)
? 認証を受けた送信者であるかどうかを確認しない (neutral)

表1に SPF で使える演算子の一覧を示しました。 送信可否に併記したものは内部的な評価結果で、注目すべきは softfail と fail の違いです。 softfail と fail はどちらも認証を受けた送信者としては扱われませんが、 softfail のほうが fail よりも意味合いが弱く、メールサーバーの設定によっては softfail のメールは受け取るが、 fail のメールは受け取らないとなっている場合があります。

メカニズム

表2. SPF のメカニズム
メカニズム マッチする条件 引数要否
all すべての場合 不要
include 引数で渡されたドメインの SPF レコードを評価して pass になった場合 必要
a 引数で渡されたドメインを正引き (A/AAAA) 結果の IP アドレスが送信者と一致する場合 検証中のドメインが暗黙的に使われるため不要だが、指定することも可能
mx 引数で渡されたドメインの MX レコードに指定されたメールサーバーを正引きした結果の IP アドレスが送信者と一致する場合 検証中のドメインが暗黙的に使われるため不要だが、指定することも可能
ip4 引数で渡された IPv4 アドレスが送信者と一致する場合 必要
ip6 引数で渡された IPv6 アドレスが送信者と一致する場合 必要
exists 引数で渡されたドメインに A レコード ( IPv6 によって接続を受けたとしても) が存在する場合 必要
ptr Do not use 引数で渡されたドメインが検証済みドメイン [1] か、そのサブドメインである場合 検証中のドメインが暗黙的に使われるため不要だが、指定することも可能

表2に SPF で使われるメカニズムの一覧と、それぞれメカニズムが使われる条件を示しました。 ptr は、参照する DNS レコードの数がほかよりも多いことや、 arpa ルートサーバーにクエリが集中することなどから Do not use とされていることに注意してください。

また、 a, mx, ip4, ip6 では CIDR 形式で IP アドレスの範囲を表現することもできます。

[1]送信者の IP アドレスを逆引きして得られたドメインのうち、それを正引きした結果が送信者の IP アドレスと一致するもの

マクロ

SPF 自体の説明が長くなりましたが、ここからが本題です。 メカニズムの説明を見る限りでは、ドメイン単位でしか認証できない様な印象を受けると思います。 また、マクロを説明している文章は珍しいため、 RFC に当たらない限りそのように勘違いしている方も多くいらっしゃるものと想像します。

しかし、 SPF にはマクロという仕組みが定義されていて、これを使うことによって実に細かく SPF の評価結果を制御することができます。

表3. SPF で使用できるマクロの一覧
マクロ 展開される結果
%{s} 送信者が SMTP の MAIL FROM コマンドで名乗ったメールアドレス
%{l} %{s} の @ よりも左側の部分 (メールアドレスのアカウント部分、ローカルパートという)
%{o} %{s} の @ よりも右側の部分 (メールアドレスのドメイン部分)
%{d} 評価中の SPF レコードが設定されているドメイン
%{i} IPv4 の場合は 8bit ずつ10進数で、 IPv6 の場合は 4bit ずつ16進数で表現し、各パートを "." で連結した送信者の IP アドレス
%{p} Do not use %{i} を逆引きした結果のドメイン
%{v} %{c} が IPv4 アドレスの場合は "in-addr", IPv6 アドレスの場合は "ipv6" という文字列
%{h} 送信者が SMTP の HELO/EHLO コマンドで名乗ったドメイン
%{c} 送信者の IP アドレス
%{r} SPF 検証を行っているメールサーバーのドメイン
%{t} 現在のタイムスタンプ

表3に SPF で使用できるマクロの一覧を示しました。 これも %{p} が ptr メカニズムと同様の理由で Do not use となっていることに注意してください。

これらのマクロを含んだ文字列を各メカニズムの引数として渡してやることで、実に多彩で手の込んだ設定ができるということは想像に難くないと思います。

r トランスフォーマー

また、マクロには "r" トランスフォーマーというものも定義されています。 これは、各マクロの値を "." で区切ってリバースさせるというもので、例えば IPv4 アドレスの場合 %{ir}.%{v}.arpa という文字列を評価すると、そのまま逆引き時のクエリを導くことができます。 デフォルトでは "." で区切られますが、マクロの閉じカーリーブレイスの直前に区切り文字を指定すればその文字列で区切ったものをリバースし、 "." でつなぎあわせたものが得られます。

スライス

さらに、マクロの値を "." で区切ったパーツのうち、先頭から何個までを使うかを指定することもできます。 クライアントの IPv4 アドレスが 192.0.2.200 の時に %{l3} を評価すると 192.0.2 を得ることができます。 r トランスフォーマー同様、区切り文字を指定することもできます。

マクロの例

送信者が名乗ったメールアドレスが strong-bad@email.example.com で、送信者の IP アドレスが IPv4 の場合 192.0.2.3 、 IPv6 の場合は 2001:db8::cb01 で、送信者の IP アドレスを逆引きした結果が mx.example.org の場合の例を RFC7208 Sender Policy Framework §7.4 より引用して以下に示します。

macro                       expansion
  -------  ----------------------------
  %{s}     strong-bad@email.example.com
  %{o}                email.example.com
  %{d}                email.example.com
  %{d4}               email.example.com
  %{d3}               email.example.com
  %{d2}                     example.com
  %{d1}                             com
  %{dr}               com.example.email
  %{d2r}                  example.email
  %{l}                       strong-bad
  %{l-}                      strong.bad
  %{lr}                      strong-bad
  %{lr-}                     bad.strong
  %{l1r-}                        strong

  macro-string                                               expansion
  --------------------------------------------------------------------
  %{ir}.%{v}._spf.%{d2}             3.2.0.192.in-addr._spf.example.com
  %{lr-}.lp._spf.%{d2}                  bad.strong.lp._spf.example.com

  %{lr-}.lp.%{ir}.%{v}._spf.%{d2}
                      bad.strong.lp.3.2.0.192.in-addr._spf.example.com

  %{ir}.%{v}.%{l1r-}.lp._spf.%{d2}
                          3.2.0.192.in-addr.strong.lp._spf.example.com

  %{d2}.trusted-domains.example.net
                               example.com.trusted-domains.example.net

  IPv6:
  %{ir}.%{v}._spf.%{d2}                               1.0.b.c.0.0.0.0.
  0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6._spf.example.com

SPF を設定する上で注意する点

SPF を設定する上で気をつけていただきたい点として、 DoS 攻撃に対応するためひとつの SPF レコードを評価するために発行できる DNS クエリの数が制限されているということがあります。 そのため、ディレクティブをたくさん並べて凝った設定をしても、左側のいくつかしか使われていなかったり、あるいはエラーとして評価自体されていないということがあります。 RFC7208 Sender Policy Framework §4.6.4 ではメカニズム毎に細かく発行できる DNS クエリの上限が決められています。 多くの場合、この制限は厳しすぎるため緩める設定が行われていると思いますが、それでも制限は存在するので気をつけてください。

おわりに

マクロを使うことによって如何に詳細に SPF レコードを設定することがお分かりいただけたかと思います。

読み返してみて気づいたのですが、 SPF の modifier に関する説明が抜けていました。 特に exp modifier は SPF の検証に失敗した場合のメール送信者への通知を自然言語で設定するための機構で、マクロを存分に活用できます 興味があれば RFC7208 を調べてみてください。

ありがとうございました。