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. The -G option does nothing before Postfix 2.3.
 7 INSPECT_DIR=/var/spool/filter
 8 SENDMAIL="/usr/sbin/sendmail -G -i" # NEVER NEVER NEVER use "-t" here.
 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

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

単純なコンテンツフィルタのパフォーマンス

上に示したようなシェルスクリプトで、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

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

この例で、"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
        -o disable_mime_output_conversion=yes
        -o smtp_generic_maps=

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

コンテンツフィルタは 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,no_milters
        -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 filtered with service1:dest1
    1.2.3.4:smtp  inet  n       -       n       -       -       smtpd
        -o content_filter=service1:dest1 
        -o receive_override_options=no_address_mappings

    # SMTP service for domains that are filtered with service2:dest2
    1.2.3.5:smtp  inet  n       -       n       -       -       smtpd
        -o content_filter=service2:dest2
        -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 機能を無効にしなければいけません。そうしないとコンテンツフィルタリングループを起こしてしまいます。

制限: