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 のキューをあらわす。煩雑にならないよう、ローカルから投稿する経路は簡潔にしてある(概要ドキュメントにもっと完全な記述がある)。
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 $ 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 ごとのエラーハンドリングなどをサポートするだろう。
Information in this section:
この節の情報:
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 その他の署名を壊すかもしれない。もし FILTER_README で説明されているような SMTP ベースのフィルタを使っているなら、高度なコンテンツフィルタに書かれているように master.cf で "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 の代替品は将来提供されるかもしれない。
メールフィルタは 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 ごとのエラーハンドリングなどをサポートするかもしれない。