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

Counter[counter] (since 2006.05.28)

Google

JAPANESE

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 ?

smtp_wrapper is a daemon program as smtp proxy for UNIX-like systems. smtp_wrapper supports smtp filtering of spam(,virus etc...).

I'm very inspired from ` The Selective SMTP Rejection (S25R) System by Mr. ASAMI Hideo'. It's very simple and light weight way for filtering spam. There is operative example with postfix and qmail in S25R's page. but, unfortunately there is no operative example with sendmail !

It's hard to use MILTER with sendmail for me, so I decided to develop this :-)

Basic policy

Download

Install

config of sendmail

make sure adding following line into sendmail.cf

O DaemonPortOptions=Port=8025, Name=MTA

make sure adding following line into submit.cf

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

Execute

Figure

* see with fixed width font ;-)
            +-----------------------------------------------------------+
            |                                                           |
            |                                                           |
 +-----+ 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 |                              |
            |          +--------+        |                              |
            |                  +---------+                              |
            |                                                           |
            +-----------------------------------------------------------+

Summary of spam measures

Filter

Basic structure of filter program specified by `-if' option

Mainly this filter checks rightness of IP address of outer MTA (by S25R).

Basic structure of filter program specified by `-f' option

Mainly this filter checks rightness of contents of messages.

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
#
# S25R check script
#
#============================================================
#
# signal trap
#
trap "spam_exit 1 \"kill by signal\"" INT HUP TERM QUIT
#============================================================
#
# init PATH
#
PATH="/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin"
export PATH
#============================================================
#
# smtp_wrapper directory
#
smtp_wrapper_dir="/usr/local/smtp_wrapper"
#
# temporary spool directosr
tmp="/var/tmp"
#
# syslog ID
sl_ident="smtp_filter1"
#
# right host's IP
#
# ex. ^127\.0\.0\.1$
#     ^192\.168\.0\.[0-9]*$
#
white_ip_db="${smtp_wrapper_dir}/white_ip_db"
#
# right FQDN
#
# ex. ^myh\.no-ip\.org$
#
white_hostname_db="${smtp_wrapper_dir}/white_hostname_db"
#
# unright host's IP
#
# ex. ^172\.21\.0\.123$
#
black_ip_db="${smtp_wrapper_dir}/black_ip_db"
#
# unright FQDN
#
# ex. ^mail\.spammer\.org$
#
black_hostname_db="${smtp_wrapper_dir}/black_hostname_db"
#
#============================================================
#
# constants
#
cr=`echo -n -e '\r'`; export cr
tab=`echo -n -e '\t'`; export tab
pid="$$"; export pid
#============================================================
#
# exit by 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"
#
# You may use `sleep' for S25R+tarpitting(http://d.hatena.ne.jp/stealthinu/20060706/p5)
# instead of `echo'
#
#   sleep 60s
#------------------------------------------------------------
    rm -f ${tmp}/smtp_filter1.*.$$.tmp
    exit $1
}
#============================================================
#
# spam check by S25R
#
spam_check ()
{
    if [ "X${from_ip}" = "X" ]
    then
        spam_exit 2 "unknown ip" # *** exit ***
    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" # *** exit ***
        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" # *** exit ***
    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" # *** exit ***
        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" # *** exit S25R rule 1 ***
        fi
#
        spam_host=`echo ${from_hostname} |\
        	egrep -i '^[^\.]*[0-9]{5}'`
        if [ "X${spam_host}" != "X" ]
        then
            spam_exit 7 "rule 2" # *** exit S25R rule 2 ***
        fi
#
        spam_host=`echo ${from_hostname} |\
        	egrep -i '^([^\.]+\.)?[0-9][^\.]*\.[^\.]+\..+\.[a-z]'`
        if [ "X${spam_host}" != "X" ]
        then
            spam_exit 8 "rule 3" # *** exit S25R rule 3 ***
        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" # *** exit S25R rule 4 ***
        fi
#
        spam_host=`echo ${from_hostname} |\
        	egrep -i '^[^\.]*[0-9]\.[^\.]*[0-9]\.[^\.]+\..+\.'`
        if [ "X${spam_host}" != "X" ]
        then
            spam_exit 10 "rule 5" # *** exit S25R rule 5 ***
        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" # *** exit S25R rule 6 ***
        fi
    fi
}
#============================================================
#
# Main proc
#
from_ip=""
from_hostname=""
white_ip=""
white_hostname=""
black_ip=""
black_hostname=""
#
#============================================================
#
# S25R check
#
from_ip=${SW_FROM_IP}
#
spam_check
#============================================================
#
# terminate and exit
#
rm -f ${tmp}/smtp_filter1.*.$$.tmp
exit 0

smtp_filter2.sh(sample)(anonymous relay check)

#! /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
#
# contents check(anonymous relay check)
#
#============================================================
#
# signal trap
#
trap "spam_exit 1 \"kill by signal\"" INT HUP TERM QUIT
#============================================================
#
# init PATH
#
PATH="/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin"
export PATH
#============================================================
#
# smtp_wrapper directory
#
smtp_wrapper_dir="/usr/local/smtp_wrapper"
#
# temporary spool directosr
tmp="/var/tmp"
#
# syslog ID
sl_ident="smtp_filter2"
#
# relay allow IP
#
# ex. ^127\.0\.0\.1$
#     ^192\.168\.0\.[0-9]*$
#
relay_allow_host="${smtp_wrapper_dir}/relay_allow_host_db"
#
# allow part of address for outer MTA
#
# ex. myh.no-ip.org
#
relay_allow_address="${smtp_wrapper_dir}/relay_allow_address_db"
#============================================================
#
# constant
#
cr=`echo -n -e '\r'`; export cr
tab=`echo -n -e '\t'`; export tab
pid="$$"; export pid
#============================================================
#
# spam exit
#
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
}
#============================================================
#
# anonymous relay check
#
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" # *** exit ***
			fi
		done
	fi
}
#============================================================
#
# Main proc
#
from_ip=""
from_hostname=""
#
#============================================================
#
# spool all messages into temporary file
#
cat >${tmp}/smtp_filter2.1.$$.tmp
#============================================================
#
# anonymous relay check
#
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
#
#============================================================
#
# If right messages, output it
#
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)
    }
}'
#
#============================================================
#
# terminate and exit
#
rm -f ${tmp}/smtp_filter2.*.$$.tmp
exit 0

BUGS


m-ito@myh.no-ip.org

[]