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>
spam対策としては既にいくつかの定番的手法が確立しています。クライアン ト側からのアプローチとしてはベイズ理論を利用したフィルタプログラム + procmailの組合せであったり、サーバ側からのアプローチとしてはMILTER対 応した同様のフィルタ + sendmailの組合せであったりするようです。
ベイズ理論を利用したフィルタは実際、非常に効果があります。ただし、学 習効果を積み重ねて行くうちにデータベースが肥大化していき、徐々に動き が重くなって来るような気がします。そんなおり、ウェブ上で以下の情報を 見つけました。
但し、上記の方法で一時的に拒否されたSMTPサーバの内、リトライアクセスす るものに関してはホワイトリスト等の手法での救済を意識する必要が有ります。
で、実際にどのように実装するかということになると、上記のページではMTA がpostfixまたはqmailである事が前提となる説明がされていて、sendmailと の組合せとなると自力でなんとかしなければならないようです。
正攻法で行くなら、MILTERを利用したフィルタを作成するべきでしょう。お そらく最も高いパフォーマンスでspamチェックをこなしてくれると思います。
MILTERを利用するにはlibmilterを利用してC言語でフィルタを作成します。 ドキュメントはsendmailのソースディレクトリ中のlibmilter/docs/以下に 有りますので、それを参考に作業します.....が、ごめんなさい。私には MILTERを理解してフィルタを書くことが出来ません、というかドキュメン トを読解する気力がありませんでした(^_^;。ですので別のアプローチで 実装することにしました。
実装するに当たって以下の点に留意しました。
Usage : smtp_wrapper [-mh hostname] [-mp port] [-q backlog] [-sh smtpserver_hostname] [-sp smtpserver_port] [-t timeout_sec] [-d delay_sec] [-if ip_filter] [-f contents_filter] [-cm child_max] [-i minimum_interval_sec] [-F] -mh hostname : my hostname [ANY] -mp port : my port [25] -q backlog : socket queue number [5] -sh smtpserver_hostname : real smtp hostname [localhost] -sp smtpserver_port : real smtp port [8025] -t timeout_sec : timeout second [no timeout] -d delay_sec : delay second for initial connection [0] -if ip_filter : filter program for IP check [/usr/local/smtp_wrapper/smtp_ip_filter] -f contents_filter : filter program for contents check [/usr/local/smtp_wrapper/smtp_contents_filter] -cm child_max : max number of connection to real smtp daemon [10] -i minimum_interval_sec : minimum interval second of connection from same ip address [0] -nsc : no sequence error check -F : run in foreground
if [ -x /usr/local/smtp_wrapper/smtp_wrapper ] then /usr/local/smtp_wrapper/smtp_wrapper \ -t 60 \ -d 10 \ -if /usr/local/smtp_wrapper/smtp_filter1.sh \ -f /usr/local/smtp_wrapper/smtp_filter2.sh \ -cm 10 \ -i 5 echo ' smtp_wrapper' fi
+-----------------------------------------------------------+ | | | | +-----+ 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 | | | +--------+ | | | +---------+ | | | +-----------------------------------------------------------+
421 Service not available, closing transmission channel(rejected by rapidly access. IP=%s)(SW01)\r\n
450 This message was rejected according to site policy(rejected by ip_filter. IP=%s)(SW02)\r\n
450 This message was rejected according to site policy(rejected by seq error. IP=%s)(SW05)\r\n
450 This message was accepted partly, but it was rejected according to site policy(rejected by contents_filter. IP=%s)(SW09)\r\n 450 This message was accepted all, but it was rejected according to site policy(rejected by contents_filter. IP=%s)(SW10)\r\n
#! /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
#! /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
fetchmail -S localhost/8025 ほにゃららみたいにして、smtp_wrapperをすっ飛ばして受信したほうが良さそうです。
その辺り、本番運用で利用される場合はくれぐれも御自身で検討し、必要 な修正を施した上で利用してください。