smtp_wrapper-0.4 -- SMTP filtering(spam, virus etc) support daemon for UNIX-like systems

参照回数[counter] (since 2006.05.28)

ENGLISH?

smtp_wrapper-0.4 -- SMTP filtering(spam, virus etc) support daemon for UNIX-like systems

Copyright (C) 2006 Masahiko Ito

These programs is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

These programs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with these programs; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

Mail suggestions and bug reports for these programs to
"Masahiko Ito" <m-ito@myh.no-ip.org>

History

What's this ?

主にspam対策としてsmtp serverと自作のspamチェックスクリプトを連係さ せるためのデーモンプログラムです。

spam対策としては既にいくつかの定番的手法が確立しています。クライアン ト側からのアプローチとしてはベイズ理論を利用したフィルタプログラム + procmailの組合せであったり、サーバ側からのアプローチとしてはMILTER対 応した同様のフィルタ + sendmailの組合せであったりするようです。

ベイズ理論を利用したフィルタは実際、非常に効果があります。ただし、学 習効果を積み重ねて行くうちにデータベースが肥大化していき、徐々に動き が重くなって来るような気がします。そんなおり、ウェブ上で以下の情報を 見つけました。

上記のページ内容を自分なりに要約した結果、 という3手法のみでも、かなり高い確立でspamを排除できるのではないかと 考えました。しかも、上記の方法はメール内容そのものは判断しないので、 チェックにかかる負荷も非常に軽いことが予想されます。

但し、上記の方法で一時的に拒否されたSMTPサーバの内、リトライアクセスす るものに関してはホワイトリスト等の手法での救済を意識する必要が有ります。

で、実際にどのように実装するかということになると、上記のページではMTA がpostfixまたはqmailである事が前提となる説明がされていて、sendmailと の組合せとなると自力でなんとかしなければならないようです。

正攻法で行くなら、MILTERを利用したフィルタを作成するべきでしょう。お そらく最も高いパフォーマンスでspamチェックをこなしてくれると思います。

MILTERを利用するにはlibmilterを利用してC言語でフィルタを作成します。 ドキュメントはsendmailのソースディレクトリ中のlibmilter/docs/以下に 有りますので、それを参考に作業します.....が、ごめんなさい。私には MILTERを理解してフィルタを書くことが出来ません、というかドキュメン トを読解する気力がありませんでした(^_^;。ですので別のアプローチで 実装することにしました。

実装するに当たって以下の点に留意しました。

とりあえず志は低く、上記の3点を実現するためにsmtp proxy的なデーモン を作ってみました。

Download

Install

sendmailの設定

sendmail.cfに以下の行が追加されている事を確認

O DaemonPortOptions=Port=8025, Name=MTA

submit.cfに以下の行が追加されている事を確認

O DaemonPortOptions=Port=8025, Name=NoMTA, Addr=127.0.0.1, M=E

起動

Figure

※固定幅フォントで見てね
            +-----------------------------------------------------------+
            |                                                           |
            |                                                           |
 +-----+ command  +------------------+  command   +----------------+    |
 |     |--------->|(1)            [2]|----------->|                |    |
 | MTA |SOCKET(25)|   smtp_wrapper   |SOCKET(8025)|       MTA      |    |
 |(MUA)|<---------|[1]            (2)|<-----------| (sendmail etc) |    |
 |     | response |                  |  response  +----------------+    |
 +-----+    |     |     (3)  [3]     |                                  |
            |     +------+----+------+                                  |
            |            A P  |                                         |
            |            | I  |                                         |
            |            | P  |                                         |
            |            | E  |                                         |
            |   (stdout) |    V (stdin)                                 |
            |          +--------+                                       |
            |          |   IP   |--------+                              |
            |          | FILTER |CONTENTS|                              |
            |          |        | FILTER |                              |
            |          +--------+        |                              |
            |                  +---------+                              |
            |                                                           |
            +-----------------------------------------------------------+

spam対策機能(のまとめ)

Filter

`-if'で指定するフィルタの基本的な構造

主に接続元のIPアドレスに対するチェックを行います(S25R)。

`-f'で指定するフィルタの基本的な構造

主にコンテンツ内容に対するチェックを行います(第三者リレーチェック)。

補足

smtp_filter1.sh(sample)(S25R方式)

#! /bin/ash
#
# This part of smtp_wrapper is distributed under GNU General Public License.
#
# Sample spam filter script
#   by "Masahiko Ito" <m-ito@myh.no-ip.org>
#
# Thanks to http://www.gabacho-net.jp/anti-spam/anti-spam-system.html
#
# IPに対するチェック(S25R)を行う場合のスクリプト。`-if'オプションに指定する。
#
#============================================================
#
# 割り込み時の処理設定
#
trap "spam_exit 1 \"kill by signal\"" INT HUP TERM QUIT
#============================================================
#
# 実行パス設定
#
PATH="/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin"
export PATH
#============================================================
#
# smtp_wrapperディレクトリ指定
#
smtp_wrapper_dir="/usr/local/smtp_wrapper"
#
# テンポラリスプールディレクトリ
tmp="/var/tmp"
#
# syslog出力時のID
sl_ident="smtp_filter1"
#
# 正当ホストのIPデータベース
#
# ex. ^127\.0\.0\.1$
#     ^192\.168\.0\.[0-9]*$
#
white_ip_db="${smtp_wrapper_dir}/white_ip_db"
#
# 正当ホストのFQDNデータベース
#
# ex. ^myh\.no-ip\.org$
#
white_hostname_db="${smtp_wrapper_dir}/white_hostname_db"
#
# 不正ホストのIPデータベース
#
# ex. ^172\.21\.0\.123$
#
black_ip_db="${smtp_wrapper_dir}/black_ip_db"
#
# 不正ホストのFQDNデータベース
#
# ex. ^mail\.spammer\.org$
#
black_hostname_db="${smtp_wrapper_dir}/black_hostname_db"
#
#============================================================
#
# ある種の定数
#
cr=`echo -n -e '\r'`; export cr
tab=`echo -n -e '\t'`; export tab
pid="$$"; export pid
#============================================================
#
# spamと判断時に終了させる処理
#
spam_exit ()
{
    logger -p mail.info -t ${sl_ident} "[$$]:IP=${from_ip}"
    logger -p mail.info -t ${sl_ident} "[$$]:HOST=${from_hostname}"
    logger -p mail.info -t ${sl_ident} "[$$]:EXIT=$1"
    logger -p mail.info -t ${sl_ident} "[$$]:REASON=$2"
#------------------------------------------------------------
#
    echo "[$$]:IP=${from_ip} HOST=${from_hostname} EXIT=$1 REASON=$2"
#
# 上記の`echo'コマンドの代りに、以下の様にスリープをかけると、
# S25R+tarpittingによるスパム対策(http://d.hatena.ne.jp/stealthinu/20060706/p5)
# を取り入れたスクリプトになる。
#
#   sleep 60s
#
#------------------------------------------------------------
    rm -f ${tmp}/smtp_filter1.*.$$.tmp
    exit $1
}
#============================================================
#
# スパムチェック処理
#
spam_check ()
{
    if [ "X${from_ip}" = "X" ]
    then
        spam_exit 2 "unknown ip" # *** 送信ホストのIPが判らない ***
    fi
#
    for i in `cat ${black_ip_db} |\
    	egrep -v '^#'`
    do
        black_ip=`echo "${from_ip}" |\
    	egrep -i "$i"`
        if [ "X${black_ip}" != "X" ]
        then
            spam_exit 3 "black IP" # *** 送信ホストIPが黒IPデータベースに登録されている ***
        fi
    done
#
    for i in `cat ${white_ip_db} |\
    	egrep -v '^#'`
    do
        white_ip=`echo "${from_ip}" |\
    	egrep -i "$i"`
        if [ "X${white_ip}" != "X" ]
        then
            break;
        fi
    done
#
    from_hostname=`host ${from_ip} 2>/dev/null |\
    	egrep -i 'domain name pointer' |\
    	head -1 |\
    	sed -e 's/^.* domain name pointer *//;s/\.$//'`
    if [ "X${white_ip}" = "X" -a "X${from_hostname}" = "X" ]
    then
        spam_exit 4 "no dns" # *** 送信ホストのIPが逆引きできない ***
    fi
#
    for i in `cat ${black_hostname_db} |\
    	egrep -v '^#'`
    do
        black_hostname=`echo "${from_hostname}" |\
    	egrep -i "$i"`
        if [ "X${black_hostname}" != "X" ]
        then
            spam_exit 5 "black hostname" # *** 送信ホスト名が黒ホスト名データベースに登録されている ***
        fi
    done
#
    for i in `cat ${white_hostname_db} |\
    	egrep -v '^#'`
    do
        white_hostname=`echo "${from_hostname}" |\
    	egrep -i "$i"`
        if [ "X${white_hostname}" != "X" ]
        then
            break;
        fi
    done
#
    if [ "X${white_ip}" = "X" -a "X${white_hostname}" = "X" ]
    then
#
        spam_host=`echo ${from_hostname} |\
        	egrep -i '^[^\.]*[0-9][^0-9\.]+[0-9].*\.'`
        if [ "X${spam_host}" != "X" ]
        then
            spam_exit 6 "rule 1" # *** [ルール1] 逆引きFQDNの最下位(左端)の名前が、数字以外の文字列で分断された二つ以上の数 ***
        fi
#
        spam_host=`echo ${from_hostname} |\
        	egrep -i '^[^\.]*[0-9]{5}'`
        if [ "X${spam_host}" != "X" ]
        then
            spam_exit 7 "rule 2" # *** [ルール2] 逆引きFQDNの最下位の名前が、5個以上連続する数字を含む ***
        fi
#
        spam_host=`echo ${from_hostname} |\
        	egrep -i '^([^\.]+\.)?[0-9][^\.]*\.[^\.]+\..+\.[a-z]'`
        if [ "X${spam_host}" != "X" ]
        then
            spam_exit 8 "rule 3" # *** [ルール3] 逆引きFQDNの上位3階層を除き、最下位または下位から2番目の名前が数字で始まる ***
        fi
#
        spam_host=`echo ${from_hostname} |\
        	egrep -i '^[^\.]*[0-9]\.[^\.]*[0-9]-[0-9]'`
        if [ "X${spam_host}" != "X" ]
        then
            spam_exit 9 "rule 4" # *** [ルール4] 逆引きFQDNの最下位の名前が数字で終わり、かつ下位から2番目の名前が、1個のハイフンで分断された二つ以上の数字列を含む ***
        fi
#
        spam_host=`echo ${from_hostname} |\
        	egrep -i '^[^\.]*[0-9]\.[^\.]*[0-9]\.[^\.]+\..+\.'`
        if [ "X${spam_host}" != "X" ]
        then
            spam_exit 10 "rule 5" # *** [ルール5] 逆引きFQDNが5階層以上で、下位2階層の名前がともに数字で終わる ***
        fi
#
        spam_host=`echo ${from_hostname} |\
        	egrep -i '^(dhcp|dialup|ppp|adsl)[^\.]*[0-9]'`
        if [ "X${spam_host}" != "X" ]
        then
            spam_exit 11 "rule 6" # *** [ルール6] 逆引きFQDNの最下位の名前が「dhcp」、「dialup」、「ppp」、または「adsl」で始まり、かつ数字を含む ***
        fi
    fi
}
#============================================================
#
# 主処理開始
#
from_ip=""
from_hostname=""
white_ip=""
white_hostname=""
black_ip=""
black_hostname=""
#
#============================================================
#
# 直接SMTP接続分のチェック
#
# (smtp_wrapper から環境変数 SW_FROM_IP に接続元IPが渡される)
#
from_ip=${SW_FROM_IP}
#
spam_check
#============================================================
#
# 後始末して終了
#
rm -f ${tmp}/smtp_filter1.*.$$.tmp
exit 0

smtp_filter2.sh(sample)(第三者リレーチェック)

#! /bin/ash
#
# This part of smtp_wrapper is distributed under GNU General Public License.
#
# Sample spam filter script
#   by "Masahiko Ito" <m-ito@myh.no-ip.org>
#
# Thanks to http://www.gabacho-net.jp/anti-spam/anti-spam-system.html
#
# コンテンツに対するチェック(第三者リレー)を行う場合のスクリプト。`-f'オプションに指定する。
#
#============================================================
#
# 割り込み時の処理設定
#
trap "spam_exit 1 \"kill by signal\"" INT HUP TERM QUIT
#============================================================
#
# 実行パス設定
#
PATH="/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin"
export PATH
#============================================================
#
# smtp_wrapperディレクトリ指定
#
smtp_wrapper_dir="/usr/local/smtp_wrapper"
#
# テンポラリスプールディレクトリ
tmp="/var/tmp"
#
# syslog出力時のID
sl_ident="smtp_filter2"
#
# 全てのリレーを許可するホストのIPデータベース
#
# ex. ^127\.0\.0\.1$
#     ^192\.168\.0\.[0-9]*$
#
relay_allow_host="${smtp_wrapper_dir}/relay_allow_host_db"
#
# 外部のサーバに許可する配送先アドレスデータベース
#
# ex. myh.no-ip.org
#
relay_allow_address="${smtp_wrapper_dir}/relay_allow_address_db"
#============================================================
#
# ある種の定数
#
cr=`echo -n -e '\r'`; export cr
tab=`echo -n -e '\t'`; export tab
pid="$$"; export pid
#============================================================
#
# spamと判断時に終了させる処理
#
spam_exit ()
{
    if [ -r ${tmp}/smtp_filter2.*.$$.tmp ]
    then
        logger -p mail.info -t ${sl_ident} "[$$]:`cat ${tmp}/smtp_filter2.*.$$.tmp | head -50 | egrep -i '^MAIL *FROM:|^RCPT *TO:|^From:|^To:|^Subject:|^Date:'`"
    else
        logger -p mail.info -t ${sl_ident} "[$$]:`cat | head -50 | egrep -i '^MAIL *FROM:|^RCPT *TO:|^From:|^To:|^Subject:|^Date:'`"
    fi
    logger -p mail.info -t ${sl_ident} "[$$]:IP=${from_ip}"
    logger -p mail.info -t ${sl_ident} "[$$]:HOST=${from_hostname}"
    logger -p mail.info -t ${sl_ident} "[$$]:EXIT=$1"
    logger -p mail.info -t ${sl_ident} "[$$]:REASON=$2"
    rm -f ${tmp}/smtp_filter2.*.$$.tmp
    echo "[$$]:IP=${from_ip} HOST=${from_hostname} EXIT=$1 REASON=$2"
    exit $1
}
#============================================================
#
# 第三者リレーチェック処理
#
relay_check ()
{
	allow_ip=""
	for i in `cat ${relay_allow_host}|\
		sed -e "s/${tab}/ /g;s/  */ /g" |\
		cut -d ' ' -f 1`
	do
		allow_ip=`echo ${from_ip} |\
			egrep "$i"`
		if [ "X${allow_ip}" != "X" ]
		then
			break
		fi
	done
#
	if [ "X${allow_ip}" = "X" ]
	then
		for i in `cat ${tmp}/smtp_filter2.1.$$.tmp |\
			awk 'BEGIN{
				cr = ENVIRON["cr"]
			}
			{
				if (length($0) == 1 && index($0, cr) > 0){
					exit;
				}else{
					print;
				}
			}' |\
			egrep -i '^RCPT *TO:' |\
			sed -e 's/ *//g`
		do
			for j in `cat ${relay_allow_address}`
			do
				allow_rcpt=""
				allow_rcpt=`echo $i |\
					egrep "$j"`
				if [ "X${allow_rcpt}" != "X" ]
				then
					break;
				fi
			done
			if [ "X${allow_rcpt}" = "X" ]
			then
				spam_exit 12 "no relay" # *** 第三者中継拒否 ***
			fi
		done
	fi
}
#============================================================
#
# 主処理開始
#
from_ip=""
from_hostname=""
#
#============================================================
#
# メール本文を全文受信(スプール)する
#
cat >${tmp}/smtp_filter2.1.$$.tmp
#============================================================
#
# 第三者中継拒否
#
from_ip=${SW_FROM_IP}
from_hostname=`host ${from_ip} 2>/dev/null |\
	egrep -i 'domain name pointer' |\
	head -1 |\
	sed -e 's/^.* domain name pointer *//;s/\.$//'`
#
relay_check
#
#============================================================
#
# 正当なメールと判断されたものを出力する
#
export from_ip
export from_hostname
#
cat ${tmp}/smtp_filter2.1.$$.tmp |\
awk 'BEGIN{
    from_ip = ENVIRON["from_ip"]
    from_hostname = ENVIRON["from_hostname"]
    pid = ENVIRON["pid"]
    out_sw = 0;
}
{
    if (out_sw == 0){
        head = toupper($0);
        if (head ~ /^DATA\r$/ ||
            head ~ /^DATA$/){
            out_sw = 1;
        }
    }

    if (out_sw == 1){
        print $0;
    }
}
END{
    if (out_sw == 0){
        printf("[%d]:IP=%-s HOST=%-s REASON=NOT spam, but NO DATA\n", pid, from_ip, from_hostname)
    }
}'
#
#============================================================
#
# 後始末して終了
#
rm -f ${tmp}/smtp_filter2.*.$$.tmp
exit 0

BUGS


m-ito@myh.no-ip.org

[更新]