#!/bin/sh
#
# Copyright (C) 2007 Jason Haar <jhaar@users.sourceforge.net>
#
# V1.0
#
# This script is called by tcpserver instead of qmail-smtpd. 
# It will introduce a delay before executing qmail-smtpd
# This has been shown to reduce the amount of SPAM received by
# sites as some spammer software don't bother carrying on with
# a connection if they don't get a SMTP server response within 
# 5-20 seconds.
#
# My personal trials with this showed me such spamming software
# tends to (87%) drop out in <15 seconds, so GREETDELAY='15' is
# my suggested setting with this. Set GREETDELAY='0' for your local
# networks, and GREETDELAY='15' for the rest within smtp.rules
# and then "maketcprules". 
# You may think increasing it to 30 seconds would
# improve the "hitrate". You'd be correct - but you will also hit some
# "real" mail servers that are not RFC compliant.  Don't do it unless
# you are willing to start whitelisting broken-but-important servers
#
# Also, as spammers tend to be "send and never come back" types of events,
# qmail-delay caches the IP addresses that have connected to it, and if the
# same address shows up >10minutes later, GREETDELAY is set down to 1sec 
# - as it is more likely to be a "real" mail server than a spammer. (if not,
# it's more likely to hit your RBL rules/SpamAssassin too - so it's low
# risk.
#
# If you have "p0f" and "geoiplookup" installed, then qmail-delay 
# will also attempt to discover the OS of the SMTP client, as well as 
# the country of origin. Such values can also be used to alter how long
# qmail-delay will delay a connection, and will be passed on to Qmail-Scanner
# so that they can show up in the Syslog records too.
#
# p0f should be set to run something like:
#
# p0f -d -o /var/log/p0f -q -Q /var/run/p0f-sock -l -t -M -i eth0 \
#  tcp and dst port smtp and not src host 127.0.0.1
#
# ..and /var/run/p0f-sock needs to be writable by group "nofile" - which
# qmail-smtpd runs as. On a closed system it may be simplest to
# run "p0f -u qmail-smtpd" - i.e. run it as qmaild
# 
# If you like the OS and GeoIP features - but don't want to bother with the
# delay bit, just set GREETDELAY='0' to effectively disable that bit...

#REMEMBER: GREETDELAY expected to be a multiple of 5
#
# e.g. GREETDELAY = 0, 5, 10, 15, etc
#

if [ "`echo $TCPREMOTEIP|egrep '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'`" == "" -o "`echo $TCPREMOTEPORT|egrep '^[0-9]+$'`" == "" ]; then
	echo "must be called by tcpserver"
	exit 1
fi

export PATH="/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin"

export POF_SOCKET="/var/run/p0f-sock"

unset DELAYTIME

USER=`id -n -u`
export USER

#Set a global default delay here. If you want to override, do
#so within smtp.rules by defining GREETDELAY='number'
#
# I came up with this figure after running qmail-delay with a default
# of 30 secs for 6 months. 88% of drops occurred pre-15 secs, so that was
# good enough for me.
DEFAULT_DELAY=15

#Any trusted IP should be configured within smtp.rules
#with a GREETDELAY="0"
TRUSTED_DELAY=0

#If this SMTP server is known to only expect mail from a small subset of
#the world, you can use DELAY_DODGY_COUNTRIES to more aggressively delay
#mail from the more annoying countries
DELAY_DODGY_COUNTRIES=""

#Any "learnt" IP will have its delay time reduced to this
LEARNT_DELAY=1


#Directory in which cache of valid hosts resides
DELAY_SPOOLDIR="/var/spool/qmail-delay"
mkdir -p $DELAY_SPOOLDIR

if [ ! -w  $DELAY_SPOOLDIR ]; then
	logger -t qmail-delay "Fatal error - $DELAY_SPOOLDIR not writable by $USER. Skipping qmail-delay"
	exec qmail-smtpd ${1+"$@"}
fi

if [ "`echo $GREETDELAY|egrep '^[0-9]+$'`" == "" ]; then
	#GREETDELAY isn't defined - so use default
	GREETDELAY="$DEFAULT_DELAY"
fi

if [ "$GREETDELAY" != "" ]; then
	DELAYTIME="$GREETDELAY"
	#If the TCPLOCALPORT is "587", or RELAYCLIENT is defined,
	#then this session is trusted
	if [ "$TCPLOCALPORT" == "587" -o "`set|grep RELAYCLIENT=`" != "" ]; then
		DELAYTIME="0"
	fi
fi

export DELAYTIME=${DELAYTIME:-0}

unset REMOTE_MATCH
FIRST_OCTET="`echo $TCPREMOTEIP|cut -d. -f1`"
SECOND_OCTET="`echo $TCPREMOTEIP|cut -d. -f2`"
THIRD_OCTET="`echo $TCPREMOTEIP|cut -d. -f3`"
FORTH_OCTET="`echo $TCPREMOTEIP|cut -d. -f4`"
mkdir -p "$DELAY_SPOOLDIR/$FIRST_OCTET/$SECOND_OCTET/$THIRD_OCTET"

CACHE_FILE="$DELAY_SPOOLDIR/$FIRST_OCTET/$SECOND_OCTET/$THIRD_OCTET/$FORTH_OCTET"

unset ORIGIN REMOTE_COUNTRY

if [ "$TCPREMOTEIP" != "" ]; then
	REMOTE_COUNTRY=`geoiplookup $TCPREMOTEIP 2>/dev/null|head -1|cut -d: -f2|awk '{print $1}'|sed 's/,.*$//'`
	if [ "$REMOTE_COUNTRY" != "" ]; then
		ORIGIN="in $REMOTE_COUNTRY"
	else
		ORIGIN=""
	fi
	if [ "`echo $REMOTE_COUNTRY|grep '^\-'`" != "" ]; then
		unset REMOTE_COUNTRY
	fi
fi
export ORIGIN REMOTE_COUNTRY

unset DD REMOTE_OS REMOTE_MATCH REMOTE_DETAILS

if [ "$POF_SOCKET" != "" -a -S "$POF_SOCKET" ]; then
   #p0f might tell us what OS the remote connection is running...
   #- need the p0f socket option (-Q) enabled
   DD=`p0fq $POF_SOCKET $TCPREMOTEIP $TCPREMOTEPORT $TCPLOCALIP 25 2>/dev/null`
   REMOTE_OS=`echo "$DD"|grep Genre|sed 's/^Genre    : //'`
   REMOTE_DETAILS=`echo "$DD"|grep ^Details|sed 's/^Details  : //'`
   if [ "$REMOTE_OS" == "" ]; then
	#Whoops, the record hasn't flushed out yet  - sleep 2 sec
	sleep 2
	DD=`p0fq $POF_SOCKET $TCPREMOTEIP $TCPREMOTEPORT $TCPLOCALIP 25 2>/dev/null`
	if [ "`echo \"$DD"|grep 'not recognized'`" != "" ]; then
		unset REMOTE_OS REMOTE_DETAILS
	else
		REMOTE_OS=`echo "$DD"|grep Genre|sed 's/^Genre    : //'`
		REMOTE_DETAILS=`echo "$DD"|grep ^Details|sed 's/^Details  : //'`
	fi
   fi
   if [ ! -w "$POF_SOCKET" ]; then
	logger -t qmail-delay "WARN: $POF_SOCKET present but not writable by $USER"
	unset POF_SOCKET
   fi
   if [ "$REMOTE_OS" == "" ]; then
	REMOTE_MATCH="Unknown-OS"
   else
	REMOTE_MATCH="$REMOTE_OS/$REMOTE_DETAILS"
   fi
else
   #no matter - you're not using p0f - so we simply can't guess
   #the remote OS
   REMOTE_MATCH="Unknown-OS"
   unset POF_SOCKET
fi

if [ "`echo $REMOTE_MATCH|grep Windows`" != "" ]; then
	REMOTE_OS="Windows"
elif [ "`echo $REMOTE_MATCH|grep Linux`" != "" ]; then
	REMOTE_OS="Linux"
elif [ "`echo $REMOTE_MATCH|grep OpenBSD`" != "" ]; then
        REMOTE_OS="OpenBSD"
elif [ "`echo $REMOTE_MATCH|grep FreeBSD`" != "" ]; then
        REMOTE_OS="FreeBSD"
elif [ "`echo $REMOTE_MATCH|egrep 'SunOS|Solaris'`" != "" ]; then
        REMOTE_OS="Solaris"
elif [ "`echo $REMOTE_MATCH|egrep Google`" != "" ]; then
        REMOTE_OS="Google"
elif [ "`echo $REMOTE_MATCH|egrep Unknown-OS`" != "" ]; then
        REMOTE_OS="Unknown-OS"
else
	REMOTE_OS="Other-OS"
fi

export REMOTE_MATCH
export REMOTE_OS

#Now see if this address has been seen more than an hour ago. If so we'll
#shorten the delay as it's: 
# a> unlikely to be a repeat spammer and 
# b> if it is a spammer - the RBLs should be picking them up now
#
# this is a compromise that will mean that valid servers that are
# non-RFC compliant WRT initial timeouts will eventually still
# get their email to you. Without this feature, you would have to
#explicitly whitelist via smtp.rules, etc.

if [ -f "$CACHE_FILE" ]; then
	if [ "`find $CACHE_FILE -mmin +60`" != "" ]; then
		DELAYTIME="$LEARNT_DELAY"
	fi
fi

if [ "$DELAY_DODGY_COUNTRIES" != "" -a "$REMOTE_COUNTRY" != ""  ]; then
 if [ "`echo $DELAY_DODGY_COUNTRIES|egrep $REMOTE_COUNTRY`" != "" ]; then
        echo "Add 20 secs to SMTP connections from countries we don't expect" > /dev/null
        DELAYTIME=`expr $DELAYTIME + 20`
 fi
fi


counter=0
VALID_CONNECTION="true"
while [ $counter -lt $DELAYTIME -a $DELAYTIME -ge 5 ]
do
	sleep 5
	unset VALID_CONNECTION
	counter=`expr $counter + 5`
	VALID_CONNECTION=`netstat -nt 2>/dev/null|egrep "${TCPREMOTEIP}:${TCPREMOTEPORT}.*ESTABLISHED"`
	if [ "$VALID_CONNECTION" == "" ]; then
		#hey - they've already dropped off - no point in hanging around
		break
	fi
done

if [ ! -f "$CACHE_FILE" ]; then
	printenv > "$CACHE_FILE"
fi

if [ "$VALID_CONNECTION" != "" ]; then
	if [ $DELAYTIME -gt 0 ]; then
		logger -t qmail-delay "accepted $REMOTE_OS SMTP connection from $TCPREMOTEHOST:$TCPREMOTEIP:$TCPREMOTEPORT $ORIGIN after $counter secs"
	fi
else
	logger -t qmail-delay "lost $REMOTE_OS SMTP connection from $TCPREMOTEHOST:$TCPREMOTEIP:$TCPREMOTEPORT $ORIGIN after $counter secs"
fi

#if no P0F, remove REMOTE_OS so Qmail-Scanner doesn't record it
if [ "$POF_SOCKET" == "" ]; then
	unset REMOTE_OS
fi
exec qmail-smtpd ${1+"$@"}
