PostfixとSendmail

Spam ScannerをPostfixに接続する方法は2つあります。

  • milterとして(推奨):PostfixはSMTPセッション中、メッセージを受け取る前に、各メッセージについて問い合わせます。スパムは4xxまたは5xxの応答で拒否できるため、その処理は自分のサーバーではなく送信側のサーバーが担います。Sendmailも同じプロトコルを使います。
  • コンテンツフィルターとして:Postfixがメッセージを受け取り、spamscanner filterに渡します。フィルターはヘッダーを追加し、sendmailでメッセージを戻します。SMTPセッション中に拒否することは一切ありません。

どちらの方法でも、すべてのメッセージに次のヘッダーを追加します。

Text
X-Spam-Flag: YES
X-Spam-Score: 8.0
X-Spam-Level: ********
X-Spam-Status: Yes, score=8.0 required=5.0 tests=BAYES_99,DECEPTIVE_LINK version=7.0.0
X-Spam-Action: tag

メッセージにすでにあるX-Spam-*ヘッダーは先に削除されるため、送信者が自分のメールを問題なしと偽ることはできません。

milter#

1. milterを起動する#

Shell
npm install --global spamscanner
spamscanner milter --port 7831 --auth --subject-tag "[SPAM]" --verbose

--rejectを指定すると、拒否のしきい値(15点)に達したメッセージは451 4.7.1 Message rejected as spamで拒否されます。451は一時的なエラーです。送信者は後で再送し、誤りがあっても設定を変えれば修正できます。結果が正しいと確認できたら、--reject-code 550で恒久的に拒否できます。--quarantineを指定すると、スパムは代わりにPostfixの保留キューに入ります。

systemdのサービスにする場合は、/etc/systemd/system/spamscanner-milter.serviceに次のように記述します。

INI
[Unit]
Description=Spam Scanner milter
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/spamscanner milter --port 7831 --auth --subject-tag [SPAM]
User=spamscanner
Group=spamscanner
Restart=on-failure
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes

[Install]
WantedBy=multi-user.target
Shell
sudo useradd --system --no-create-home --shell /usr/sbin/nologin spamscanner
sudo systemctl daemon-reload
sudo systemctl enable --now spamscanner-milter

2. Postfixから使うように設定する#

/etc/postfix/main.cfに次のように記述します。

INI
smtpd_milters = inet:127.0.0.1:7831
milter_protocol = 6
# If the milter is down: accept mail unfiltered (accept) or ask senders to retry (tempfail).
milter_default_action = accept
# Long enough for a language model, if one is configured.
milter_content_timeout = 120s
Shell
sudo postfix reload

smtpd_miltersはSMTP経由で届くメールが対象です。sendmailコマンドで投入したメールもスキャンする必要がない限り、non_smtpd_miltersは空のままにしてください。

3. テストする#

swaksでテストメッセージを送れます。GTUBEは、どのスパムフィルターもスパムとして扱うテスト用の文字列です。

Shell
swaks --to you@example.com --server 127.0.0.1 \
  --body 'XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X'

--rejectを指定しなければ、メッセージはX-Spam-Flag: YESとタグ付きの件名で配送されます。--rejectを指定すると、swaksに451または550の応答が表示されます。

コンテンツフィルター#

SMTPセッション中にメールを決して拒否してはならない場合や、milterを使えないサーバーで使います。

/etc/postfix/master.cfにフィルターのサービスを追加し、SMTPのリスナーで使います。

Text
smtp      inet  n       -       n       -       -       smtpd
  -o content_filter=spamscanner:dummy
spamscanner unix -      n       n       -       10      pipe
  flags=Rq user=spamscanner null_sender=
  argv=/usr/bin/node /usr/local/lib/node_modules/spamscanner/dist/esm/cli.js filter --subject-tag [SPAM] -f ${sender} -- ${recipient}

Postfixはほとんど空の環境でフィルターを実行するため、argvではNode.jsとスクリプトをフルパスで指定します(command -v nodeとnpm root --globalで確認できます)。その後、次を実行します。

Shell
sudo postfix reload

フィルターはsendmail -G -iでメッセージを戻します。この方法で投入されたメールは再びsmtpリスナーを通らないため、二重にフィルタリングされることはありません。

終了コードでPostfixに結果を伝えます。0は配送済み、69は拒否(--reject指定時。Postfixが送信者にバウンスする)、75は一時的な失敗(Postfixがメッセージを保持して再試行する)です。スキャンや配送の失敗はすべて75になるため、設定が壊れていてもメールが失われたりバウンスしたりすることはありません。

Sendmail#

sendmail.mcに次のように記述します。

Text
INPUT_MAIL_FILTER(`spamscanner', `S=inet:7831@127.0.0.1, F=T, T=S:30s;R:30s;E:5m')

F=Tを指定すると、milterを利用できない間、Sendmailは一時的な失敗で応答します。代わりにメールをフィルタリングせずに受け入れるには、これを外してください。sendmail.cfを再生成し、Sendmailを再起動します。

スパムを迷惑メールフォルダーに振り分ける#

タグ付けだけでは、スパムは受信トレイに配送されます。Dovecotでは、Sieveのルールでスパムを移動します。

Text
require ["fileinto"];
if header :is "X-Spam-Flag" "YES" {
  fileinto "Junk";
  stop;
}

その他のメールサーバーではDovecot、Exim、Haraka、procmailを扱っています。ユーザーが迷惑メールフォルダーに出し入れしたメールから学習する方法は学習を参照してください。

テスト#

リポジトリのエンドツーエンドテストでは、実際のPostfixを動かします。ハムはヘッダー付きで配送され、偽造されたX-Spam-Flagは削除され、スパムはタグ付けされ、GTUBEはSMTPセッション中に550で拒否され、コンテンツフィルターは2つ目のポートでメールにタグを付けます。scripts/e2e-postfix.shがそのPostfixを準備し、test/e2e/postfix.test.jsがメールを送ります。

このページをGitHubで編集 · Markdown ·