Postfix キューに入った後のコンテンツフィルタ


はじめに

このドキュメントは Postfix バージョン 2.1 以降を要求します。

通常、Postfix はメールを受け取り、それをメールキューに入れてから 配送します。ここで述べる外部コンテンツフィルタを使うと、メールはキューに 入った「後で」フィルタリングされます。このアプローチはメールフィルタリング プロセスからメール受信プロセスを切り離し、並列に走らせるフィルタリング プロセスの数を最大限にします。

キューに入った後のコンテンツフィルタは次のように使われることを意図して います:

ネットワーク
または
ローカルユーザ
-> Postfix
キュー
-> コンテンツ
フィルタ
-> Postfix
キュー
-> ネットワークまたは
ローカルメールボックス

このドキュメントは一つの Postfix インスタンスを以下の全てに使っている 実装を記述しています: メールの受信やフィルタリング、配送。2つの別々の Postfix インスタンスを使う応用はこのドキュメントの後のバージョンでカバー される予定です。

キューに入った後のコンテンツフィルタと、入ってくる SMTP メールが Postfix キューに入れられる「前に」フィルタリングされる SMTPD_PROXY_README ドキュメントに 記述されたアプローチを混同しないでください。

このドキュメントは全てのEメールをフィルタリングする2つのアプローチと、 選択的にメールをフィルタリングするいくつかのオプションを記述しています:

動作原理

外部コンテンツフィルタは Postfix からフィルタリングされていないメールを 受け取り (ずっと下に記述されています)、以下のいずれかをおこないます:

  1. Postfix にメールを差し戻します。これはコンテンツや配送先を変更した 後かもしれません。

  2. (Postfix に適切な状態コードを送り返すことで) メールを拒否します。 Postfix はメールを送信者に返します。

注意: メールワームや詐称された spam が多い中、送信者アドレスがほとんどの場合は 元々のものではないため、送信者アドレスにウィルスを送り返すのは「とても悪い 考え」です。既知のウィルスは破棄し、疑わしいものは人が処理を決められるように 検疫するほうがよいでしょう。

単純なコンテンツフィルタの例

最初の例は構築が単純です。Postfix はネットワークから smtpd(8) サーバでフィルタリングされていない メールを受け取り、フィルタを Postfix pipe(8) 配送エージェントでコンテンツフィルタに配送します。コンテンツフィルタは フィルタリングされたメールを Postfix に Postfix sendmail(1) コマンドで差し挟み、Postfix が 最終的な配送先に配送できるようにします。

これは Postfix sendmail(1) コマンドを 使って投函されたメールはコンテンツフィルタリングできないことを意味します。

以下の図で、数字が続く名前は Postfix コマンドまたはデーモンプログラムを 表しています。Postfix アーキテクチャの概要は OVERVIEW ドキュメントを参照してください。

フィルタ
リング前
->

smtpd(8)

pickup(8)
>- cleanup(8) -> qmgr(8)
Postfix
キュー
-< local(8)
smtp(8)
pipe(8)
->
->
フィルタ後
フィルタ後
^
|
|
v
maildrop
キュー
<- Postfix
postdrop(1)
<- Postfix
sendmail(1)
<- コンテンツ
フィルタ

コンテンツフィルタは以下のような簡単なシェルスクリプトでも構いません:

 1 #!/bin/sh
 2 
 3 # Simple shell-based filter. It is meant to be invoked as follows:
 4 #       /path/to/script -f sender recipients...
 5 
 6 # Localize these.
 7 INSPECT_DIR=/var/spool/filter
 8 SENDMAIL="/usr/sbin/sendmail -i"
 9 
10 # Exit codes from <sysexits.h>
11 EX_TEMPFAIL=75
12 EX_UNAVAILABLE=69
13 
14 # Clean up when done or when aborting.
15 trap "rm -f in.$$" 0 1 2 3 15
16 
17 # Start processing.
18 cd $INSPECT_DIR || {
19     echo $INSPECT_DIR does not exist; exit $EX_TEMPFAIL; }
20 
21 cat >in.$$ || { 
22     echo Cannot save mail to file; exit $EX_TEMPFAIL; }
23 
24 # Specify your content filter here.
25 # filter <in.$$ || {
26 #   echo Message content rejected; exit $EX_UNAVAILABLE; }
27 
28 $SENDMAIL "$@" <in.$$
29 
30 exit $?

解説:

結果に満足するまで、まずはしばらくこのスクリプトを手で動かすことを 推奨します。実際のメッセージ (ヘッダ+本体) を入力として走らせます:

% /path/to/script -f sender recipient... <message-file

コンテンツフィルタスクリプトに満足したら:

コンテンツフィルタリングを無効にするには、master.cf ファイルを編集し、 "-o content_filter=filter:dummy" テキストを Postfix SMTP サーバを定義したエントリから取り除き、もう一度 "postfix reload" を実行します。

上に示したようなシェルスクリプトで、SMTP で到着してから出て行くまでの 通過にかかる Postfix のパフォーマンスが4倍ほど落ちます。コンテンツ フィルタリングのプロセスでさらにテンポラリファイルを作成したり削除したり するたびごとに、さらに通過のパフォーマンスが悪くなるでしょう。ローカルで 投函されたり、ローカルに配送されるメールはすでに SMTP で通過するメール よりも遅いため、パフォーマンスの影響は少ないです。

単純なコンテンツフィルタの制限

上のようなコンテンツフィルタの問題は、あまり堅牢ではないということです。 それは、ソフトウェアがしっかり定義されたプロトコルで Postfix と話をしない ためです。シェルがなんらかのメモリアロケーション問題でフィルタシェル スクリプトが止まった場合、スクリプトは /usr/include/sysexits.h にあるような 正しい終了ステータスを生成しません。メールは deferred キューに行くのでは なく、バウンスされます。同様にコンテンツフィルタ自身がリソース問題に当たった 場合も堅牢性がなくなる可能性があります。

単純なコンテンツフィルタの方式は header_checksbody_checks パターンで 呼び出されるコンテンツフィルタアクションに対しては適切ではありません。 これらのパターンは Postfix sendmail コマンドでメールが差し挟まれる際に 再び適用され、その結果メールフィルタリングループに入ってしまいます。 高度なコンテンツフィルタリングの手法 (以下参照) では、フィルタリング されたメールに対して header_checksbody_checks パターンを無効に することが可能となります。

高度なコンテンツフィルタの例

2つ目の例はかなり複雑ですが、よいパフォーマンスを出し、マシンがリソース 問題に当たったときもメールをバウンスする可能性が低くなります。この コンテンツフィルタはフィルタリングされていないメールを localhost ポート 10025 で SMTP を使って受け取り、フィルタリングされたメールを localhost ポート 10026 で SMTP を使って Postfix に差し戻します。

SMTP が使えないコンテンツフィルタソフトウェアに対しては、Bennett Todd の SMTP プロキシがよい PERL/SMTP コンテンツフィルタリングフレームワークを実装して います。参照: http://bent.latency.net/smtpprox/

以下の図で、数字が続く名前は Postfix コマンドまたはデーモンプログラムを 表しています。Postfix アーキテクチャの概要は OVERVIEW ドキュメントを参照してください。

フィルタリング前

フィルタリング前
->

->
smtpd(8)

pickup(8)
>- cleanup(8) -> qmgr(8)
Postfix
キュー
-< smtp(8)

local(8)
->

->
フィルタ後

フィルタ後
^
|
|
v
smtpd(8)
10026
smtp(8)
^
|
|
v
コンテンツフィルタ 10025

ここで上げる例では、SMTP で到達したメールや Postfix sendmail コマンドを 使ってローカルで投かんされたメールを含めて、全てのメールをフィルタリング します。ローカルユーザをフィルタリングから除外する方法や、配送先に依存する コンテンツフィルタの設定方法は、このドキュメントの最後の方にある例を 参照してください。

テンポラリファイルを作らないのであれば、SMTP で到着してから出て行く までの通過にかかる Postfix のパフォーマンスが2倍ほど落ちることが想定 されます。テンポラリファイルを作るごとに数倍パフォーマンスが失われます。

高度なコンテンツフィルタ: 全てのメールをフィルタリングさせる

全てのメールに対して高度なコンテンツフィルタ方式を有効にするには、 次のように main.cf に指定します:

/etc/postfix/main.cf:
    content_filter = scan:localhost:10025
    receive_override_options = no_address_mappings

コンテンツフィルタリングを無効にするには、main.cf の上の2行を削除 またはコメントアウトします。高度なコンテンツフィルタリングに対するその他 全ての変更はコンテンツフィルタリングが無効になっている時には効果が ありません。

高度なコンテンツフィルタ: フィルタリングされていないメールをコンテンツ フィルタに送る

この例で、"scan" は少し異なる設定パラメータを持つ Postfix SMTP クライアントのインスタンスです。このように Postfix master.cf ファイルで サービスを設定します:

/etc/postfix/master.cf:
    # =============================================================
    # service type  private unpriv  chroot  wakeup  maxproc command
    #               (yes)   (yes)   (yes)   (never) (100)
    # =============================================================
    scan      unix  -       -       n       -       10      smtp
        -o smtp_send_xforward_command=yes

高度なコンテンツフィルタ: コンテンツフィルタを動かす

コンテンツフィルタは Postfix の inetd と同等な Postfix spawn サービスを 使って、設定することができます。例えば、localhost ポート 10025 で待つ、最大 10 のコンテンツフィルタリングプロセスは次のようになります:

/etc/postfix/master.cf:
    # ===================================================================
    # service       type  private unpriv  chroot  wakeup  maxproc command
    #                     (yes)   (yes)   (yes)   (never) (100)
    # ===================================================================
    localhost:10025 inet  n       n       n       -       10      spawn
        user=filter argv=/path/to/filter localhost 10026

Postfix のかわりにあなたのフィルタで localhost:10025 ポートを listen したいのであれば、あなたのフィルタをスタンドアロンプログラムとして起動 しなければならず、また Postfix spawn サービスを使ってはいけません。

高度なコンテンツフィルタ: Postfix にメールを差し戻す

適切な診断でメールをバウンスするか、ローカルホストの 10026 ポートで 待っている専用のリスナを通してメールを Postfix に返すことがコンテンツ フィルタの仕事です。

最も単純なコンテンツフィルタは、単に入力と出力の間で SMTP コマンドや データをコピーするものです。問題があった場合にやらねばならないのは、 Postfix からの `.' の入力に対して `550 content rejected' と応答し、 Postfix にメールを差し戻す接続で `.' を送らずに切断することだけです。

/etc/postfix/master.cf:
    # ===================================================================
    # service       type  private unpriv  chroot  wakeup  maxproc command
    #                     (yes)   (yes)   (yes)   (never) (100)
    # ===================================================================
    localhost:10026 inet  n       -       n       -       10      smtpd
        -o content_filter= 
        -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
        -o smtpd_helo_restrictions=
        -o smtpd_client_restrictions=
        -o smtpd_sender_restrictions=
        -o smtpd_recipient_restrictions=permit_mynetworks,reject
        -o mynetworks=127.0.0.0/8
        -o smtpd_authorized_xforward_hosts=127.0.0.0/8

高度なコンテンツフィルタのパフォーマンス

ここで述べたコンテンツフィルタリングの "サンドイッチ" アプローチでは、 利用可能な CPU やメモリ、I/O リソースに対してフィルタの並列度がマッチ していることが重要です。コンテンツフィルタリングプロセスが少なすぎると、 流量が少なくても active キュー にメールがたまってしまいます; 並列数が大きすぎると、リソースが不十分で プロセスが落ちてしまい、コンテンツフィルタ宛のメールが遅延することに なってしまいます。

今のところ、コンテンツフィルタのパフォーマンスチューニングは試行錯誤です; フィルタリングされたメッセージとフィルタリングされていないメッセージで 同じキューを共有しているため、分析するには不都合です。このドキュメントの 概要で触れたように、複数の Postfix インスタンスを使ったコンテンツ フィルタリングが将来のバージョンでカバーされます。

外部ユーザからのメールのみをフィルタリングする

最も簡単な方法は、master.cf で複数の SMTP サーバ IP アドレスを使う 「1つの」Postfix インスタンスを設定することです:

この後は、main.cf ファイルで "content_filter" や "receive_override_options" を指定してはいけないことを除いて、上に概要が示された "高度な" または "単純な" コンテンツフィルタリングの例と同じ手順に従うことができます。

ドメインごとに異なるフィルタを使う

あなたが MX サービスを提供していて、ドメインごとに異なるコンテンツ フィルタを適用したいのであれば、master.cf で複数の SMTP サーバ IP アドレスを持つ「1つの」Postfix インスタンスを設定することができます。 それぞれのアドレスは異なるコンテンツフィルタサービスを提供します。

/etc/postfix.master.cf:
    # =================================================================
    # service     type  private unpriv  chroot  wakeup  maxproc command
    #                   (yes)   (yes)   (yes)   (never) (100)
    # =================================================================
    # SMTP service for domains that are content filtered with foo:bar
    1.2.3.4:smtp  inet  n       -       n       -       -       smtpd
        -o content_filter=foo:bar 
        -o receive_override_options=no_address_mappings

    # SMTP service for domains that are content filtered with xxx:yyy
    1.2.3.5:smtp  inet  n       -       n       -       -       smtpd
        -o content_filter=xxx:yyy 
        -o receive_override_options=no_address_mappings

この後は、main.cf ファイルで "content_filter" や "receive_override_options" を指定してはいけないことを除いて、上に概要が示された "高度な" または "単純な" コンテンツフィルタリングの例と同じ手順に従うことができます。

それぞれのドメインを適切な SMTP サーバインスタンスに向けるように、DNS の MX レコードを設定します。

access またはヘッダ/本体テーブルにおける FILTER アクション

上のフィルタリング設定は静的なものです。決められた道筋に従うと、 メールは常にフィルタリングされるか全くされないかのどちらかです。 Postfix 2.0 では動的にもコンテンツフィルタリングを有効にできるように なりました。

access(5) テーブルのルールでコンテンツフィルタリングを有効にするには:

/etc/postfix/access:
    whatever       FILTER foo:bar

header_checks(5) または body_checks(5) テーブルのパターンで コンテンツフィルタリングを有効にするには:

/etc/postfix/header_checks:
    /whatever/     FILTER foo:bar

cleanup サーバのヘッダ/本体チェックと同様に、smtpd access マップでも これをおこなうことができます。この機能は細心の注意を払って使わなければ いけません: フィルタリング後の smtpd や cleanup デーモンで全ての UCE 機能を無効にしなければいけません。そうしないとコンテンツフィルタリング ループを起こしてしまいます。

制限: