PositfixでのSPAM対策
(キュー投入前コンテンツフィルタ)

白水啓章 作成 2019/08/05 更新 2023/11/17
手元ではいくつか新機能を追加して使っていますが、特に利用報告が無いため、こちらのリポジトリ内容はそのままになっています。
最新版が見たい等ありましたら、issue 等でご連絡ください。

概要

「Postfix キュー投入前コンテンツフィルタ」の枠組みを利用したSPAM対策
https://shirouzu.jp/tech/content_filter

「Postfix キュー投入前コンテンツフィルタ」とは

仕組みの説明はこちらをご覧ください。 http://www.postfix-jp.info/trans-2.2/jhtml/SMTPD_PROXY_README.html

Postfix内部に入り込んて、通信を中継します。 そして「必要があれば」それを変更します。(man in the middle のようなもの)

それを SPAMフィルタとして使うメリットは下記の通り。

  1. header_checks/body_checks(=1行毎の判断)よりも柔軟(=メール全体での判断が可能)。
  2. SPAMと見做した時点ではまだSMTP通信中のため、直接エラーコードを返せる。
    (「キュー投入後コンテンツフィルタ」と違い、「受信のSMTP通信で成功」を返さない&エラーメールも発生せず)

content_filter.py の特徴

  1. base64 や quoted-printable をデコードした後の判定が可能
  2. (メールヘッダだけでなく)SMTPレベルの MAIL FROM: / RCPT TO:、さらに XFORWARD(逆引き名、IPアドレス、ポート、HELO内容等)を含む判断が可能
  3. SPAM判定となり、エラーを返して受信拒否した場合も、全体の受信内容をファイルとして保存可能。
    (つまり、SPAM条件が適正だったか正確な事後調査が可能。spam_dat.DBG=1以上で有効)
  4. ホワイトリストによる除外指定が可能。
  5. syslog に SPAM判定されたメールの message-id 及び、マッチした正規表現リストを出力。

無償・無保証で、ご自由にお使いください。

(SMTPにさほど詳しく無いので、フィードバック歓迎します)

content_filter.py の使い方

1.spam_dat.py.sampleを参考に、設定ファイル(spam_dat.py)を適宜、作成/変更します。

2.content_filter.py を起動します。

3.master.cf の smtp行を下記に書き換えます。

smtp      inet  n       -       n       -       20      smtpd
     -o smtpd_proxy_filter=127.0.0.1:60025
     -o smtpd_client_connection_count_limit=20

4.master.cf に下記を追記します。

127.0.0.1:60026 inet n  -       n       -        -      smtpd
        -o smtpd_authorized_xforward_hosts=127.0.0.0/8
        -o smtpd_recipient_restrictions=permit_mynetworks,reject
        -o smtpd_data_restrictions=permit_mynetworks
        -o mynetworks=127.0.0.0/8

5.Postfix をリスタートします。

6.syslog(mail.log) の content_filter出力を確認します。

設定ファイル(spam_dat.py)でのマッチ指定書式

下記の書式で、ホワイトリスト定義(WHITE_HEAD/WHITE_DATA)とSPAM定義(CHECK_HEAD/CHECK_DATA)を指定。 (HEADはヘッダのみ検査、DATAはヘッダ&ボディを検査)

CHECK_DATA = [
  [ b'正規表現1_1', b'正規表現1_2,... ],  # ルール1
  [ b'正規表現2_1', b'正規表現2_2,... ],  # ルール2
      :
  [ b'正規表現n_1', b'正規表現2_2,... ],  # ルールn
]
  1. 指定は正規表現で行う
  2. 1ルールにつき、1つ以上の正規表現文字列(バイト列)を列挙
  3. 1ルール内の全要素がマッチ(AND条件)= そのルールにマッチ
  4. どれか1つのルールにマッチすると、判定終了

それ以外の設定項目の説明は spam_dat.py を参照してください。

運用後の改善

運用中にSPAMメールが通過した時、spam_dat.py にマッチパターンを追加して対応しますが、検証に -f オプションが使えます。 DBGレベルを2以上に設定していると、全てのメールに関して、下記のような SMTP通信記録が残ります。

content_filter[18302]: smtp_log for msg_id= to /tmp/content_filter/smtp_20190811_134429_0.txt

これを使って、

content_filter -f /tmp/content_filter/smtp_20190811_134429_0.txt

などとすると、下記のように同じメールが届いた際に SPAM判定されるかどうかを事前判定できます。

SPAM判定される場合: SPAM is detected. msg_id=<xxxx> CHECK_HEAD(1) = [ regex_pattern1, regex_pattern2... ]

SPAM判定されない場合: pass msg_id=<xxxx>