Postfix before-queue Milter support


はじめに

Postfix バージョン 2.3 では Sendmail バージョン 8 の Milter (mail filter) プロトコルのサポートを導入する。このプロトコルは MTA の外側のアプリケーションでメールの内容だけでなく SMTP のイベント(接続、切断)、SMTP コマンド(HELO、MAIL FROM など)を検査するのに使われる。これはすべてメールがキューに入る前におこなわれる。

Postfix に Milter サポートを追加した理由は、望まないメールをブロックするだけでなく、信憑性を検証したり(例:SenderID+SPFDomain keys)、メールに電子署名したり(例: Domain keys)といった既存アプリケーションがたくさんあることによる。そういったもろもろのソフトウェアごとにもうひとつ Postfix 固有のバージョンを持つのは人的およびシステムのリソースの無駄遣いである。

Postfix 2.3 は Sendmail バージョン 8 の Milter プロトコルのバージョン 4 までのすべての要求のうち、以下のひとつを除くすべてを実装している: メッセージ本文の置換。いずれにせよ、本ドキュメントの最後にある 回避方法制限の節を参照してほしい。

この文書は次の話題についての情報を提供する。

どうやって Milter アプリケーションを Postfix と接続するか

Postfix の Milter 実装は異なるふたつのメールフィルタのリストを使う。リストのひとつめは SMTP メールでだけ使われるフィルタで、もうひとつのリストは非 SMTP メールで使われるものである。ふたつのリストの適用先は異なっていて都合がよくない。これを避けるには Postfix に重大な再構築が必要になるだろう。

Postfix の構造に精通している人のために、以下に Milter アプリケーションがどのように Postfix とつながるかの図を示す。名前のうしろに数字がついているものは Postfix コマンドまたはサーバプログラムであり、影つきの箱の中の名前に数字のないものは Postfix のキューをあらわす。煩雑にならないよう、ローカルから投稿する経路は簡潔にしてある(概要ドキュメントにもっと完全な記述がある)。

SMTP 専用
フィルタ
非 SMTP
フィルタ
^
|
|
v
^
|
|
|
|
|
|
v
ネットワーク -> smtpd(8)
\
ネットワーク -> qmqpd(8) -> cleanup(8) -> incoming
/
pickup(8)
:
ローカル -> sendmail(1)

Milter アプリケーションのビルド

Milter アプリケーションは C や JAVA、Perl で書かれているかもしれないが、この文書では C アプリケーションだけを扱う。Milter アプリのために Sendmail 8 Milter プロトコルを実装したオブジェクトライブラリが必要になる。Postfix は現在そのようなライブラリを提供してないが、Sendmail にはある。

いくつかの Linux や *BSD ディストリビューションには Sendmail の libmilter ライブラリがデフォルトでインストールされている。これを使えば、ヘタにいじりまわさなくても dk-miltersid-milter のようなアプリケーションができあがる。

$ gzcat dk-milter-x.y.z.tar.gz | tar xf -
$ cd dk-milter-x.y.z
$ make
[...出力は略...]

ほかのプラットホームではふたつの選択肢がある:

Milter アプリケーションを動作させる

Milter アプリケーションを動かすには、オプションのためフィルタのドキュメントを参照すること。典型的なコマンドはこのようになる:

# /some/where/dk-filter -u userid -p inet:portnumber@localhost ...other options...

userid に他のアプリケーションで使われていない値("postfix" や "www" などでない値)を指定してください。

Postfix の設定

Sendmail と同じく、Postfix にはどのように Milter アプリケーションとやりとりするかを制御する設定オプションが多数ある。初期の Postfix の Milter プロトコル実装では、たくさんのオプションがグローバル、すなわちすべての Milter アプリケーションに適用される。将来の Postfix バージョンでは Milter ごとのタイムアウトや Milter ごとのエラーハンドリングなどをサポートするだろう。

Information in this section:

この節の情報:

SMTP 専用 Milter アプリケーション

SMTP 専用 Milter アプリケーションは Postfix smtpd(8) サーバをとおって届いたメールを扱う。これは典型的にはいらないメールをフィルタしたり、承認された SMTP クライアントからのメールに署名したりするのに使われる。Postfix smtpd(8) サーバをとおって届いたメールは次に述べる非 SMTP フィルタは適用されない。

注記: Postfix が自分で付加した Received: メッセージヘッダを除去するために header_checks(5) の IGNORE アクションを使ってはならない。これはメール署名フィルタで問題を起こす。かわりに、Postfix の Received: メッセージヘッダは残し、情報を無害化するために header_checks(5) の REPLACE アクションを使いなさい。

SMTP 専用 Milter アプリケーション(ひとつ以上)は smtpd_milters パラメータに指定する。それぞれの Milter アプリケーションは listen しているソケットの名前で識別される。それ以外の Milter 設定オプションは後の節で議論する。Milter アプリケーションは指定された順に適用され、コマンドを拒否した最初の Milter アプリケーションが他の Milter アプリケーションからの応答に優先する。

/etc/postfix/main.cf:
    # Milters for mail that arrives via the smtpd(8) server.
    # See below for socket address syntax.
    smtpd_milters = inet:localhost:portnumber ...other filters...

listen するソケットの一般的な文法は以下のようになる:

unix:pathname

指定されたパス名でバインドされているローカルの UNIX ドメインサーバへの接続。もし smtpd(8) ないしは cleanup(8) プロトコルが chroot して動作していれば、絶対パスは Postfix のキューディレクトリからの相対パスとして解釈される。

inet:host:port

指定したローカルないしはリモートホストの指定した TCP ポートへの接続。ホストとポートは数字か名前で指定する。

注記: Postfix の文法は inet:port@host という形式の Milter の文法とは異なる。

非 SMTP Milter アプリケーション

非 SMTP フィルタは Postfix sendmail(1) のコマンドラインや Postfix qmqpd(8) サーバをとおって届いメールを扱う。典型的にはメールの電子署名に使われる。非 SMTP フィルタを不要なメールのフィルタに使うこともできるが、 この節で後述するような制限がある。Postfix smtpd(8) サーバをとおって届いたメールは非 SMTP フィルタは適用されない。

注記: Postfix が自分で付加した Received: メッセージヘッダを除去するために header_checks(5) の IGNORE アクションを使ってはならない。これはメール署名フィルタで問題を起こす。かわりに、Postfix の Received: メッセージヘッダは残し、情報を無害化するために header_checks(5) の REPLACE アクションを使いなさい。

非 SMTP Milter アプリケーションは non_smtpd_milters パラメータに指定する。このパラメータは前節の smtpd_milters と同じ文法を使う。SMTP 専用フィルタと同じように、ひとつ以上の Milter アプリケーションを指定でき、指定した順に適用され、コマンドを拒否した最初の Milter アプリケーションが他のアプリケーションからの応答に優先する。

/etc/postfix/main.cf:
    # Milters for non-SMTP mail.
    # See below for socket address syntax.
    non_smtpd_milters = inet:localhost:portnumber ...other filters...

非 SMTP メールで Milter アプリケーションを使うときにひとつ小さなめんどうごとがある: SMTP セッションがない。Milter アプリケーションをうまく動かすために、Postfix cleanup(8) サーバは実はなんと SMTP クライアントの接続と切断のイベントおよび、SMTP クライアントの EHLO、MAIL FROM、RCPT TO、DATA コマンドをシミュレートしている。

これは一般的には期待どおりに動くが、ひとつだけ例外がある: 非 SMTP フィルタはシミュレートされた RCPT TO コマンドを拒否したり、一時的失敗にしたりしてはならない。non_smtpd_milters アプリケーションが受信者を拒否したり一時的失敗したりすると、Postfix は設定エラーを報告し、メールはキューに留まるだろう。

メールに電子署名するメールフィルタではこの問題は起きない。

Milter のエラーの扱い

Milter アプリケーションのエラーを Postfix がどのように扱うかは milter_default_action パラメータで指定する。指定しなければ、クライアントが後で再試行できるように一時エラーのステータスを応答するという動作になる。フィルタがなかったかのようにメールを受けとりたければ "accept" を指定し、恒久エラーのステータスでメールを拒否するには "reject" とする。

    # What to do in case of errors? Specify accept, reject, or tempfail.
    milter_default_action = tempfail

Milter プロトコルバージョン

Postfix は Sendmail の libmilter ライブラリを使ってビルドされるわけではないので、Postfix が使うべき Milter プロトコルのバージョンを設定する必要があるかもしれない。指定しなければバージョンは2になる。

milter_protocol = 2

もし Postfix の milter_protocol で設定したバージョンが小さすぎると、libmilter ライブラリはこのようなエラーメッセージをログに記録するだろう:

application name: st_optionneg[xxxxx]: 0xyy does not fulfill action requirements 0xzz

直すには Postfix の milter_protocol のバージョン番号を増やせばよい。しかしながら、Postfix でサポートされていない機能もあるので、下にある制限の節も参照されたい。

もし Postfix の milter_protocol で設定したバージョンが大きすぎると、libmilter ライブラリは警告をログに出力せず単純にハングアップし、Postfix は以下のどちらかの警告メッセージを出力する:

postfix/smtpd[21045]: warning: milter inet:host:port: can't read packet header: Unknown error : 0
postfix/cleanup[15190]: warning: milter inet:host:port: can't read packet header: Success

直すには Postfix の milter_protocol のバージョン番号をもっと小さくすればよい。

Milter プロトコルのタイムアウト

Postfix は Milter のプロトコルのそれぞれの段階で異なる制限時間がある。この表はどのタイムアウトがありいつ使われるかを示したものである(EOH = ヘッダの終わり、EOM = メッセージの終わり)。

パラメータ 制限時間 プロトコルの段階
milter_connect_timeout 30s CONNECT
milter_command_timeout 30s HELO, MAIL, RCPT, DATA, UNKNOWN
milter_content_timeout 300s HEADER, EOH, BODY, EOM

注意: DNS 検索を多くおこなうアプリケーションでは30秒は大きくない。しかしながら、上のタイムアウトを大きく増やしすぎると、リモート SMTP クライアントがあきらめてメールが何回も配送されるかもしれない。これはキュー投入前フィルタではもともとある問題である。

Sendmail マクロのエミュレーション

Postfix は限られた数の Sendmail マクロを表に示したようにエミュレートする。SMTP プロトコルのの段階ごとに異なるのマクロが利用できる(EOM = メッセージの終わり)。これらが利用できるかは Sendmail と常に同じというわけではない。解決するには下の回避方法の節を参照のこと。

名前 利用できる場面 説明
i DATA, EOM キュー ID
j いつでも myhostname の値
{auth_authen} MAIL, DATA, EOM SASL ログイン名
{auth_author} MAIL, DATA, EOM SASL 送信者
{auth_type} MAIL, DATA, EOM SASL ログインメソッド
{client_addr} いつでも クライアント IP アドレス
{client_connections} CONNECT このクライアントの同時接続数
{client_name} いつでも クライアントホスト名 (検索、照合に失敗したときは "unknown")
{client_ptr} CONNECT, HELO, MAIL, DATA クライアントの逆引き名(検索に失敗したときは "unknown")
{cert_issuer} HELO, MAIL, DATA, EOM TLS クライアント証明書の issuer (発行者)
{cert_subject} HELO, MAIL, DATA, EOM TLS クライアント証明書の subject
{cipher_bits} HELO, MAIL, DATA, EOM TLS セッション鍵のサイズ
{cipher} HELO, MAIL, DATA, EOM TLS 暗号化方式
{daemon_name} いつでも milter_macro_daemon_name の値
{mail_addr} MAIL 送信者アドレス
{rcpt_addr} RCPT 受信者アドレス
{tls_version} HELO, MAIL, DATA, EOM TLS プロトコルバージョン
v いつでも milter_macro_v の値

Postfix は SMTP プロトコルの段階ごとに特定のマクロ一式を送る。このマクロは表に説明したようなパラメータで設定される(EOM = メッセージの終わり)。

パラメータ名 プロトコルバージョン プロトコルの段階
milter_connect_macros 2 以上 CONNECT
milter_helo_macros 2 以上 HELO/EHLO
milter_mail_macros 2 以上 MAIL FROM
milter_rcpt_macros 2 以上 RCPT TO
milter_data_macros 4 以上 DATA
milter_end_of_data_macros 2 以上 EOM
milter_unknown_command_macros 3 以上 不明なコマンド

回避方法

コンテンツフィルタが DomainKey その他の署名を壊すかもしれない。もし FILTER_README で説明されているような SMTP ベースのフィルタを使っているなら、高度なコンテンツフィルタに書かれているように master.cf で "disable_mime_output_conversion = yes" を指定するべきである。

Sendmail の Milter アプリケーションはもともと Sendmail バージョン 8 用に開発されたものであり、Postfix とは構造が異なる。結果として、いくつかの Milter アプリケーションは Postfix では成り立たない環境を前提にしているものがある。

制限

この節では Postfix の Milter 実装における制限を列挙する。いくつかの制限は時間をかけて実装が進むうちに取り除かれるだろう。もちろん、キュー投入前フィルタリングの通常の制限は常に適用される。解説は CONTENT_INSPECTION_README を参照のこと。