Postfix バージョン 2.3 では Sendmail バージョン 8 の Milter (mail filter) プロトコルのサポートを導入します。このプロトコルはMTAの外側のアプリケーションでメールの内容だけでなくSMTPのイベント (接続、切断)、SMTP コマンド (HELO、MAIL FROM など) を検査するのに使われます。これはすべてメールがキューに入る前におこなわれます。
Postfix に Milter サポートを追加した理由は、望まないメールをブロックするだけでなく、信憑性を検証したり (例: SenderID+SPF と Domain keys)、メールに電子署名したり (例: Domain keys) といった既存アプリケーションがたくさんあるためです。そういったもろもろのソフトウェアごとにもうひとつ Postfix 固有のバージョンを持つのは人的およびシステムのリソースの無駄遣いです。
Postfix 2.3 は Sendmail バージョン 8 の Milter プロトコルのバージョン 4 までのすべての要求のうち、以下のひとつを除くすべてを実装しています: メッセージ本文の置換。いずれにせよ、本ドキュメントの最後にある回避方法と制限の節を参照してください。
このドキュメントは以下の話題に関する情報を提供します:
Postfixの Milter 実装は異なるふたつのメールフィルタのリストを使います: リストのひとつめはSMTPメールでだけ使われるフィルタで、もうひとつのリストは非SMTPメールで使われるものです。ふたつのリストの適用先は異なっていて都合がよくありません。これを避けるには Postfix に重大な再構築が必要になるでしょう。
SMTP 専用フィルタはPostfix smtpd(8) サーバをとおって届いたメールを扱います。これは典型的にはいらないメールをフィルタしたり、承認された SMTP クライアントからのメールに署名したりするのに使われます。SMTP だけの Milter アプリケーションは後述するように smtpd_milters パラメータで指定します。Postfix smtpd(8) サーバをとおって届いたメールは次に述べる非 SMTP フィルタは適用されません。
非 SMTP フィルタはPostfix sendmail(1) のコマンドラインやPostfix qmqpd(8) サーバをとおって届いたメールを扱います。典型的にはメールの電子署名にのみ使われます。非 SMTP フィルタを不要なメールのフィルタに使うこともできますが、 SMTP 専用のフィルタと比較すると制限があります。非 SMTP 用 Milter アプリケーションは後述するように non_smtpd_milters パラメータで指定します。
Postfix の構造に精通している人のために、以下に Milter アプリケーションがどのように Postfix とつながるかの図を示します。数字が続く名前は Postfixコマンドまたはサーバプログラムであり、影つきの箱の中の名前に数字のないものは Postfix のキューを表します。煩雑にならないよう、ローカルから投稿する経路は簡潔にしてあります (OVERVIEW ドキュメントにもっと完全な記述があります)。
SMTP 専用
フィルタ非 SMTP
フィルタ
^
||
v
^
|
|
||
|
|
vネットワーク -> smtpd(8) \ ネットワーク -> qmqpd(8) -> cleanup(8) -> incoming / pickup(8) : ローカル -> sendmail(1)
Milter アプリケーションは C や JAVA、Perl で書かれているかもしれませんが、この文書では C アプリケーションだけを扱います。Milter アプリのために Sendmail 8 Milter プロトコルを実装したオブジェクトライブラリが必要になります。Postfix は現在そのようなライブラリを提供していませんが、Sendmail にはあります。
いくつかの Linux や *BSD ディストリビューションには Sendmail の libmilter ライブラリがデフォルトでインストールされています。これを使えば、ヘタにいじりまわさなくても dk-milter や sid-milter のようなアプリケーションができあがります:
$ gzcat dk-milter-x.y.z.tar.gz | tar xf - $ cd dk-milter-x.y.z $ make [...出力は略...]
ほかのプラットホームではふたつの選択肢があります:
Sendmail の libmilter オブジェクトライブラリとインクルードファイルをインストールします。Linux システムでは、libmilter は Sendmail-devel パッケージとして提供されているかもしれません。libmilter をインストールした後、前述したように Milter アプリケーションをビルドします。
Sendmail の libmilter ライブラリはインストールせず、かわりに Sendmail のソースコードからライブラリをビルドする:
$ gzcat sendmail-x.y.z.tar.gz | tar xf - $ cd sendmail-x.y.z/libmilter $ make [...出力は略...]
自分の libmilter ライブラリをビルドした後で、続けて libmilter のインクルードファイルとオブジェクトライブラリの場所を指定するように Milter アプリケーションのソース配布にあるインストール手順に従います。典型的には、sid-filter/Makefile.m4 や似たような名前のファイルでこれらの設定をいじります:
APPENDDEF(`confINCDIRS', `-I/some/where/sendmail-x.y.z/include') APPENDDEF(`confLIBDIRS', `-L/some/where/sendmail-x.y.z/obj.systemtype/libmilter')
その後で Milter アプリケーションをビルドします。
Milter アプリケーションを動かすには、オプションのためフィルタのドキュメントを参照してください。典型的なコマンドはこのようになります:
# /some/where/dk-filter -u userid -p inet:portnumber@localhost ...other options...
userid に他のアプリケーションで使われていない値 ("postfix" や "www" などでない値) を指定してください。
Sendmail と同じく、Postfix にはどのように Milter アプリケーションとやりとりするかを制御する設定オプションが多数あります。初期の Postfix の Milter プロトコル実装では、たくさんのオプションがグローバル、すなわちすべての Milter アプリケーションに適用されます。将来の Postfix バージョンでは Milter ごとのタイムアウトや 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 フィルタは 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 コマンドをシミュレートしています。
新しいメールが sendmail(1) コマンドからメールが届くと、Postfix cleanup(8) は IP アドレスが "127.0.0.1" の "localhost" から ESMTP でメールが届いたかのように見せかけます。結果は Sendmail バージョン 8.12 以降のコマンドラインからの投稿で起きることに Sendmail がこの結果に至るまでのメカニズムとは異なるものの、非常によく似たものになります。
新しいメールが qmqpd(8) サーバから届くと、Postfix cleanup(8) サーバは QMQPD クライアントのホスト名と IP アドレスから ESMTP でメールが届いたかのようにふるまいます。
古いメールが "postsuper -r" でキューに再投入されると、Postfix cleanup(8) サーバは新しいメールとして届いたときに使われたのと同じクライアント情報を使います。
これは一般的には期待どおりに動きますが、ひとつだけ例外があります: 非 SMTP フィルタはシミュレートされた RCPT TO コマンドを拒否したり、一時的失敗にしたりしてはいけません。 non_smtpd_milters アプリケーションが受信者を拒否したり一時的失敗したりすると、Postfix は設定エラーを報告し、メールはキューに留まります。
メールに電子署名するメールフィルタではこの問題は起きません。
Milter アプリケーションのエラーを Postfix がどのように扱うかは milter_default_action パラメータで指定します。デフォルトでは、クライアントが後で再試行できるように一時エラーのステータスを応答するという動作になります。フィルタがなかったかのようにメールを受けとりたければ "accept" を指定し、恒久エラーのステータスでメールを拒否するには "reject" とします。
# What to do in case of errors?Specify accept, reject, or tempfail. milter_default_action = tempfail
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 のバージョン番号をもっと小さくします。
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 クライアントがあきらめてメールが何回も配送されるかもしれません。これはキュー投入前フィルタではもともとある問題である。
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} 送信者アドレス {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 その他の署名を壊すかもしれません。SMTPベースのコンテンツフィルタを使っているなら、advanced content filterの例に書かれているように master.cf に "-o disable_mime_output_conversion=yes" (注意: "=" の前後に空白を置かない) を加えるべきです。
Sendmail の Milter アプリケーションはもともと Sendmail バージョン 8 用に開発されたものであり、Postfix とは構造が異なります。結果として、いくつかの Milter アプリケーションは Postfix では成り立たない環境を前提にしているものがあります。
いくつかの Milter アプリケーションはローカルからのメールを認識するのに "{if_addr}" マクロを使います; このマクロは Postfix には存在しません。回避策: かわりに "{client_addr}" マクロを使います。
いくつかの Milter アプリケーションはこのような警告ログを出力します:
sid-filter[36540]: WARNING: sendmail symbol 'i' not available
さらに、このような "unknown-msgid" を含むメッセージヘッダを挿入するかもしれません:
X-SenderID: Sendmail Sender-ID Filter vx.y.z host.example.com <unknown-msgid>
これは、いくつかの Milter アプリケーションが MTA が MAIL FROM (送信者) コマンドを受けつける前にキュー ID は既知であることを期待しているために起きます。一方、Postfix は最初の有効な RCPT TO (受信者) コマンドを受け付ける後までキューファイル名は決まりません。Postfix のキューファイル名は複数のディレクトリ間でユニークでなければならないので、ファイルが生成される前に名前は決まりません。もし複数のメッセージが同じキュー ID で同時に使われたとしたら、メールは失われます。
Milter アプリケーションがつける醜いメッセージヘッダの対策に、メッセージを最後まで受信した後でキュー ID を見つけるようにする小さなコードを Milter のソースに追加します。
フィルタのソースファイル (典型的には dk-filter/dk-filter.c のような名前) を編集します。
mlfi_eom() 関数を探し、以下の太字で示した部分を先頭付近に追加します:
dfc = cc->cctx_msg; assert(dfc != NULL); /* Determine the job ID for logging. */ if (dfc->mctx_jobid == 0 || strcmp(dfc->mctx_jobid, JOBIDUNKNOWN) == 0) { char *jobid = smfi_getsymval(ctx, "i"); if (jobid != 0) dfc->mctx_jobid = jobid; } /* get hostname; used in the X header and in new MIME boundaries */
注意:
メールフィルタごとに変数名が若干異なって使われます。上記のコードでコンパイルできなければ、mlfi_eoh() ルーチンの先頭を探します。
これは醜いメッセージヘッダを修正するだけで、WARNING メッセージはそのままです。幸いにも、dk-filter はメッセージを一度しかログに出力しません。
いくつかの Milter アプリケーションでは、mlfi_eoh() (あるいは WARNING をログ出力しているルーチンどこでも) の呼び出しをメッセージ終了まで遅らせることで WARNING と "unknwon-msgid" の両方を直すことが可能なものもあります。
フィルタのソースファイル (典型的には sid-filter/sid-filter.c のような名前) を編集します。
smfilter テーブルを探し、mlfi_eoh (ないしはあらゆる WARNING を出力しているルーチン) を NULL で置き替えます。
mlfi_eom() 関数を探し、以下の太字で示すように先頭付近に mlfi_eoh() を呼び出すコードを追加します:
assert(ctx != NULL); #endif /* !DEBUG */ ret = mlfi_eoh(ctx); if (ret != SMFIS_CONTINUE) return ret;
これは sid-milter-0.2.10 で動きます。ほかの Milter アプリケーションでこれをやるとコアダンプするでしょう。
この節では Postfix の Milter 実装における制限を列挙します。いくつかの制限は時間をかけて実装が進むうちに取り除かれるでしょう。もちろん、キュー投入前フィルタリングの通常の制限は常に適用されます。解説は CONTENT_INSPECTION_README を参照してください。
Postfix は現在 Sendmail 8 の Milter プロトコルバージョン2から4までだけを話すアプリケーションだけをサポートしています。ほかのプロトコルタイプやプロトコルバージョンは後で追加されるかもしれません。
C で書かれたアプリケーションでは Sendmail の libmilter ライブラリを使う必要があります。Postfix の代替品は将来提供されるかもしれません。
メールフィルタには2つあります: SMTP メールだけに使われるフィルタ (smtpd_milters パラメータで指定) および非 SMTP メール用フィルタ (non_smtpd_milters パラメータで指定)。非 SMTP フィルタは主にローカルからの投稿用です。
メールが非 SMTP フィルタで処理されるとき、Postfix cleanup(8) サーバは SMTP クライアントの接続、切断イベントおよび SMTP クライアントの EHLO、MAIL FROM、RCPT TO、DATA コマンドをシミュレートします。これはうまく動きますが、ひとつだけ例外があります: 非 SMTP フィルタはシミュレートされた RCPT TO コマンドで REJECT や TEMPFAIL してはいけません。非 SMTP フィルタが受信者を REJECT や TEMPFAIL すると、Postfix は設定エラーを報告し、メールはキューに留まります。
現在 Postfix はコンテンツフィルタを内部的な転送やエイリアスをするメールや、バウンスや Postmaster notification のように内部的に生成されるメールに適用することはできません。このようなメールに Milter で署名したい場合には問題になるでしょう。
外から SMTP で届くメールにキュー投入前コンテンツフィルタを使う場合 (SMTPD_PROXY_README 参照)、Milter アプリケーションは SMTP コマンドの情報だけ得ることができ、メッセージヘッダや本文にアクセスしたりメッセージやエンベロープを修正することはできません。
Postfix 2.3 はメッセージ本文を置換する Milter リクエストはサポートしていません。サポートしてないこの操作をリクエストする Milter アプリケーションはこのような警告ログを出力します:
application name: st_optionneg[134563840]: 0x3d does not fulfill action requirements 0x1e
解決法は、欠けている機能をサポートする Postfix バージョン (が出るのを待つこと) です。
ほとんどの Milter 設定オプションはグローバルです。将来の Postfix バージョンでは Milter ごとのタイムアウトや Milter ごとのエラーハンドリングなどをサポートするでしょう。