▼Postfix の strict_rfc821_envelopes で何が拒否されるのか▼

Postfixにて、spam対策として strict_rfc821_envelopes = yes の設定と、 「出来の悪いメーラを拒否してしまう」という記述はよく見かけます。 が、実際に何が拒否されるのか考察する資料はほとんど見かけません。

$Keywords: Postfix strict_rfc821_envelopes reject dot-user, dot-domain, excess tokens in MAIL FROM, blank username, domain only $

$Id: postfix821.html,v 1.14 2015-11-05 12:58:18+09 kabe Exp $


RFC821違反のメールアドレスとは

メールのヘッダ(To:など)に関してはもっと制限がゆるいのですが、 ユーザの目には触れない SMTPの MAIL FROM (envelope from) と RCPT TO (envelope to) に関しては制限があります。

RFC821的には問題あるアドレス
RCPT TO: <.user@domain.name> 頭に "."
RCPT TO: <user.@domain.name> ユーザ名末尾が "."
RCPT TO: <user..name@domain.name> "."が連続している
RCPT To: Hogemoge Taro <taro@domain.name> ヘッダのTo:を丸ごとコピー。
sendmailは これでも動いてしまう
RCPT TO: <hogemoge taro@domain.name> 空白をクオートしていない。
サーバによって解釈が異なるので危険
RCPT TO: <user@.domain.name> ドメイン頭に"."
RCPT TO: <@domain.name> ユーザ名が空。
PostfixではMAILER-DAEMONに配送
MAIL FROM: <ぷぎゃww@domain.name> 8bit文字。UTF-8もダメ。 クオートしてもダメ。
MAIL FROM: <#@[]> qmail の doublebounce。 []は空のIPアドレスのつもりらしい

逆に、以下のようなものは変に見えても違反とは言えません。 実際に使ってメールサーバが想定通りに動作するか、は、また別の問題。

RFC821的には問題ないアドレス
MAIL FROM: <"..do=co#!{mo :-):-)..."@domain.name>
MAIL FROM: <"<paypal@ebay.com>"@domain.name>
"でクオートすればほぼなんでもあり
MAIL FROM: <`authenumerate`@domain.name>
MAIL FROM: <%RNDCHAR16%@domain.name>
MAIL FROM: <-#@domain.name>
クオート無しで使える特殊記号もあり (空白は要クオート)
MAIL FROM: <paypal!ebay@domain.name> UUCP経路指定。UUCPは絶滅危惧種なうえ、 解釈がシステムによって違う。
MAIL FROM: <@paypal.com,@ebay.com:trustme@domain.name> source route指定。今時のメールサーバはsource routeは無視するが、 人間をだます程度の用途はある。
RFC 976, UUCP Mail Interchange Format Standard
RFC 5336, SMTP Extension for Internationalized Email Addresses (UTF8SMTP, Experimental) ではUTF-8も使えるが、広く知られているとは言いがたい。

該当RFCの記述確認

現在では RFC2821 (Simple Mail Transfer Protocol, 2001) が最新で、 こちらのほうが文法が単純なので RFC2821を参照してみましょう。

Reverse-path = Path
Forward-path = Path
Path = "<" [ A-d-l ":" ] Mailbox ">"
Mailbox = Local-part "@" Domain
Local-part = Dot-string / Quoted-string
Dot-string = Atom *("." Atom)
Quoted-string = """ *qcontent """

Domain = (sub-domain 1*("." sub-domain)) / address-literal
sub-domain = Let-dig [Ldh-str]
Let-dig = ALPHA / DIGIT
Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig
address-literal = "[" IPv4-address-literal /
	IPv6-address-literal /
	General-address-literal "]"

# これ以下の要素は RFC2822にて定義
atom            =       [CFWS] 1*atext [CFWS]
atext           =       ALPHA / DIGIT / ; Any character except controls,
                        "!" / "#" /     ;  SP, and specials.
                        "$" / "%" /     ;  Used for atoms
                        "&" / "'" /
                        "*" / "+" /
                        "-" / "/" /
                        "=" / "?" /
                        "^" / "_" /
                        "`" / "{" /
                        "|" / "}" /
                        "~"
qcontent        =       qtext / quoted-pair
qtext           =       NO-WS-CTL /     ; Non white space controls
                        %d33 /          ; The rest of the US-ASCII
                        %d35-91 /       ;  characters not including "\"
                        %d93-126        ;  or the quote character
quoted-pair     =       ("\" text) / obs-qp

NO-WS-CTL       =       %d1-8 /         ; US-ASCII control characters
                        %d11 /          ;  that do not include the
                        %d12 /          ;  carriage return, line feed,
                        %d14-31 /       ;  and white space characters
                        %d127
text            =       %d1-9 /         ; Characters excluding CR and LF
                        %d11 /
                        %d12 /
                        %d14-127 /
                        obs-text

このうち、<a-d-l> は現在のTCPでのSMTPとDNS MXを使う配送では 使いませんので、無視してかまいません。単純に <mailbox> 以下だけを 考えれば良いでしょう。

address-literalを使うメールアドレス (root@[192.168.0.0]) は設定不良の際の一時回避用なので、考察から外します。

Local-part (ユーザ名部分)は文法上はコントロールコードも使えますが、 RFC2821 [Page 37] では解説部分にて「使ってはいけない」(MUST NOT)と されています。

ここから mailbox を正規表現に書き直すとこうなります。 余計分かりにくいですが、計算機に食わせるにはこの形式でないと。

	local-part = dot-string | quoted-string
	dot-string = [A-Za-z0-9!#$%&'*+/=?^_`{|}~-]{1,}(\.[A-Za-z0-9!#$%&'*+-/=?^_`{|}~]{1,})*
	atom = [A-Za-z0-9!#$%&'*+/=?^_`{|}~-]{1,}
	domain = [A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*
	quoted-string = "(([][ !"#$%&'()*+,./0-9:;<=>?@A-Z^_`a-z{|}~-])|(\\[][ !"#$%&'()*+,./0-9:;<=>?@A-Z\^_`a-z{|}~-]))*"

	mailbox = ^(([A-Za-z0-9!#$%&'*+/=?^_`{|}~-]{1,}(\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]{1,})*)|("(([][ !"#$%&'()*+,./0-9:;<=>?@A-Z^_`a-z{|}~-])|(\\[][ !"#$%&'()*+,./0-9:;<=>?@A-Z\^_`a-z{|}~-]))*"))@[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*$

この記述で直接 egrep に食わせることが出来ますが、 シェルスクリプトに引数として埋め込むにはエスケープが大変なので、 いったん here-document でファイルに書いてからシェル変数に代入するか、 egrep -f regexpを書いたファイル とするのがよいでしょう。

考察

・ドメイン名に "_" (アンダースコア) は使えません
これは DNS の制限なのでSMTP特有の話ではありません。 ホスト名もダメです。 ユーザ名の部分は可。

・ユーザ名部分にはいろいろな特殊文字も使える
純粋なユーザ名部分に特殊文字を使うことはあまりないでしょうが、 VERP (owner-listname+user=domain@origin.domain) でよく見かける文字以外にも色々使えます。
ただ "!" "%" は経路制御用、 その他"+" や "-" など、メールサーバによって 特殊扱いされる文字もあるので、 使う際はメールサーバのマニュアルを確認しましょう。

・ユーザ名の先頭・末尾は "."に出来ない
.user@domain とか user.@domain はダメです。
特にNTTドコモが 迷惑メール対策としてこういった文法違反メールアドレスを 推奨していたため、ドコモはもちろん他のケータイキャリアでも こういったメールアドレスが存在している場合があります。

・クオートでくくればほぼ何でもあり
"abc<def>(with space)@..."@domain.name は 有効です。

ただ発信側のメールサーバでちゃんとクオートしてくれるかは 別の話。 とくに巨大プロバイダやケータイキャリアはインターネットとの接続部分に 独自開発のメールサーバを使うことが多く、往々にして 実装がおかしかったりします。 なおドコモのメールサーバがクオートを行うようになったのは 2005/06月から。 (他キャリアについては不明)

・エラーメール用の空アドレス(<>)は含まれない
ので、SMTP的には MAIL FROM:<> だけ 例外扱いされています。 なお宛先 (RCPT TO)としては空アドレスは意味が無いので使えません。
spam対策と称して「空のenvelope-fromは受け取らない」という 対策をするサイトもあるようですが、 エラーメールが誰にも届かないという大事故につながります。 やめましょう。 受信拒否されたエラーメールは、どうしようもないので捨てられます。 メールが届いていない、という事実を把握できる人がいなくなります。
現実には空のenvelope-fromのspamは多くありません。 エラーメールに見えるので、受け取っても人間が読まないため。

Postfix上の実装

さて実際に Postfix で strict_rfc821_envelopes = yes にすると どうなるかというと、

結論から言ってしまうとドコモのアドレスは受けられます。 ただ他のケータイキャリアのアドレスは受信不可になる、という報告も あるようです。

以下は Postfix 2.2.x の実装。

strict_rfc821_envelopes設定による変化
strict_rfc821_envelopes = no yes
MAIL FROM:user@domain.name ×
MAIL FROM:<"comment 1" <abc@def>>×
MAIL FROM:<(comment 2) abc@def> ×
MAIL FROM:<@domain.name> ×
MAIL FROM:<user@domain1 user@domain2>××
MAIL FROM:<user@.domain.name> ××
MAIL FROM:<8ビット文字@domain.name>
MAIL FROM:<..user..@domain.name>
RCPT TO: <-t@.domain.name> ××

なお、拒否されるアドレスが入力されると、/var/log/maillog には 以下のようなメッセージが出力されます。

	Illegal address syntax from unknown[58.61.138.35] in MAIL command: sz;o';[970689yuf@163.com

◇strict_rfc821_envelopesに関係なく許可

ただしどちらも RFC821的には禁止です。

◇strict_rfc821_envelopes = yes で追加される検査

◇strict_rfc821_envelopesに関係なく拒否

◇頭が "-"

頭が "-" (RCPT TO: <-t@dom.ain>)の アドレスは、RFC的には問題ないですが、 外部コマンド起動時にオプションと誤解されるのを予防するため、 宛先としては Postfixでは bounce 扱いします。(rejectにはならない)

Apr  1 11:55:69 sha postfix/qmgr[65538]: E79E0E33D2: to=<-t@dom.ain>, relay=none, delay=0, status=bounced (invalid recipient syntax: "-t@dom.ain")

ケータイ宛などで扱いたいなら allow_min_user = yesに設定。 ただし、利用者が.forward.procmailrc を 自由に書換えられる環境では 大穴があく可能性があるので控えましょう。

ex. sendmail -tは「電文中のTo,Cc,Bccに従って配送」なので、 メールサーバ内部の設定次第では第三者中継に使える、かも。


なぜ Postfix では <@domain> が root宛に配送されるのか

debug_peer_level を上げてコマンドを発行するとある程度判明しますが、

Postfixが RCPT TO:<@my.destination> を受信すると、 内部的にはトークン解析器によって ""@my.destination と、 ユーザ名部分がクオートされます。

空文字列は null address と解釈されるので、 empty_address_recipient (デフォルト MAILER-DAEMON) の設定にて、 内部ではさらに MAILER-DAEMON@my.destination になります。

MAILER-DAEMON は通常 /etc/aliases にて rootやpostmasterに 振り替えられているので、エラーになりません。


かべ@sra-tohoku.co.jp