#! /bin/ksh
:
#
#	checksmtp - trace an IP# or hostname through the sendmail TCP Wrappers
# 
# Note this script requires POSIX Shell features most likely provided
# by ksh on any given system.  This script also requires a local copy
# of the most recent version of 'host', as well as a working version
# of lynx.
# 
# Note that this script must be run on the host where the sendmail TCP
# Wrappers access files reside so that it has the same behaviour as
# sendmail will.
# 
# (c) Copyright 1999 Greg A. Woods.
# Freely redistibutable as-is.
# All other rights reserved.
# Return all fixes/modifications/suggestions/etc. to <woods@planix.com>.
#
#ident	"@(#)src:checksmtp.sh	1.16	99/10/12 11:47:54 (woods)"

# TODO:
# 
# - add a '-d daemon' option to allow changing the default daemon name
# from "sendmail"
# 
# - think about adding a '-R RBLs' option to accept an explicit list
# of RBLs.  Note this'll require creating a temp copy of a hosts.allow
# file in a private and safe /tmp subdirectory and then using
# 'tcpdmatch -d'
# 

argv0=`basename $0`

USAGE="Usage: $argv0 [-H] [-q] [-v] [-x] IP-or-domain ..."

HELP="$USAGE
	-q	query the appropriate RBL database for more info, if possible
	-v	be verbose and show what is happening under the hood
	-x	be even more verbose -- debugging mode
"

PATH=/usr/sbin:$PATH
export PATH

DEBUG=false
VERBOSE=false
QUERYDB=false

while getopts "Hqvx" OPTCHAR ; do
	case $OPTCHAR in
	H)
		echo "$HELP" 1>&2
		exit 2
		;;
	q)
		QUERYDB=true
		;;
	v)
		VERBOSE=true
		;;
	x)
		DEBUG=true
		;;
	\?)
		echo "$USAGE" 1>&2
		exit 2
		;;
	esac
done
shift `expr $OPTIND - 1`

if [ $# -lt 1 ] ; then
	echo "$USAGE" 1>&2
	exit 2
fi

nqueries=0
while [ $# -gt 0 ] ; do
	nqueries=$(($nqueries + 1))

	QUERY=$(echo "$1" | tr '[A-Z]' '[a-z]')
	shift

	if [ $nqueries -gt 1 ]; then
		$VERBOSE && echo ""
	fi
	$VERBOSE && echo "$argv0: processing $QUERY"

	# tcpdmatch may produce several matches so we first group them
	# into more easily parsed data lines...
	#
	datalines=$(tcpdmatch sendmail $QUERY |
		awk 'BEGIN {
				address = "";
				hostname = "";
				line = 0;
				file = "/etc/hosts.allow";
				warning = "";
			}
			$2 == "address" {
				address = $3;
				next;
			}
			$2 == "hostname" {
				hostname = $3;
				next;
			}
			$1 == "matched:" {
				line = $4;
				file = $2;
				next;
			}
			$1 == "warning:" {
				gsub(/ /, "|");
				print;
				next;
			}
			$1 == "access:" {
				if (address != "") {
					printf("%d:%s:%s:%s:%s\n", line, file, hostname, address, $2);
					address = "";
				}
			}
		' | sort -u)
	# Now for each match reported we figure out what it means and
	# do any additional lookups required....
	#
	nwarnings=0
	for line in $datalines ; do
		$DEBUG && echo "$argv0: processing tcpdmatch data: '$line'" 1>&2
		lineno=$(echo $line | awk -F: '{print $1}')
		if [ "$lineno" = "warning" ]; then
			echo "$argv0: $line" | tr '|' ' '
			if [ $nwarnings -eq 0 ]; then
				nwarnings=$(($nwarnings + 1))
				hostmatch=$(host -A $QUERY 2>&1)
				if [ $? -ne 0 -o $(expr "$hostmatch" : '.*!!!') -gt 0 -o $(expr "$hostmatch" : '.*\*\*\*') -gt 0 ] ; then
					echo "$argv0: If you found '$QUERY' in a log, then try the IP# logged with it instead."
				else
					echo "$argv0: that is very odd!  '$QUERY' seems OK! -- You should check their IP# too!"
				fi
			fi
			continue
		fi
		if [ "$lineno" -eq 0 ] ; then
			# double-check these with 'host -A', tcpdmatch
			# sometimes misses paranoid checks, especially
			# for IP numbers!
			#
			hostmatch=$(host -A $QUERY 2>&1)
			if [ $? -ne 0 -o $(expr "$hostmatch" : '.*!!!') -gt 0 -o $(expr "$hostmatch" : '.*\*\*\*') -gt 0 ] ; then
				echo "$argv0: SMTP likely blocked from ${QUERY} because of insecure DNS."
				echo "WARNING: further RBL checks suspended until their DNS is fixed."
				# have to do this again because $hostmatch lost all newlines
				host -A $QUERY
			else
				$VERBOSE && echo "$argv0: $QUERY implicitly granted SMTP access."
			fi
			continue
		fi
		file=$(echo $line | awk -F: '{print $2}')
		getline=$(echo $line | awk -F: '{printf("sed -n %sp %s", $1, $2)}')
		RBL=$($getline | awk -F: '{print $2}')
		$DEBUG && echo "$argv0: found match of '${RBL}' at line #${lineno} in ${file}." 1>&2
		host=$(echo $line | awk -F: '{print $3}')
		access=$(echo $line | awk -F: '{print $5}')
		addr=$(echo $line | awk -F: '{print $4}')
		inaddr=$(echo "$addr" | awk -F. '{printf("%d.%d.%d.%d", $4, $3, $2, $1);}')
		if [ -z "$host" ]; then
			$DEBUG && echo "$argv0: looking up PTR name for $addr." 1>&2
			host=$(host -q -i -t ptr $addr | awk '{print $3}')
		fi
		if [ X"$host" != X"$QUERY" -a X"$host" != X"paranoid" ]; then
			case "$QUERY" in
			[1-9].*|[1-9][0-9].*|[12][0-9][0-9].*)
				hostmatch=$(host -A $QUERY 2>&1)
				if [ $? -ne 0 -o $(expr "$hostmatch" : '.*!!!') -gt 0 -o $(expr "$hostmatch" : '.*\*\*\*') -gt 0 ] ; then
					echo "$argv0: SMTP may block from ${QUERY} if DNS is insecure -- try the hostname too!"
					echo "$argv0: If that name is not found then their DNS may be insecure."
					host -A $QUERY
				fi
				;;
			*)
				echo "$argv0: NOTE: PTR mismatch! '${QUERY}' != '${host}'."
				;;
			esac
		fi
		if [ "$access" = "granted" ]; then
			echo "$argv0: $QUERY explicitly granted SMTP access."
 			$DEBUG && echo "$argv0: by line #${lineno} in ${file}."
		fi
		RBLDOMAIN=$(echo $RBL | sed -e 's/{RBL}//' -e 's/ //g' | tr '[A-Z]' '[a-z]')
		case "$RBL" in
		*{RBL}*)
			$DEBUG && echo "$argv0: got a live RBL from line #${lineno} in ${file}!" 1>&2
			if [ "$access" = "granted" ]; then
				echo "$argv0: WARNING!  RBL '$RBLDOMAIN' would have blocked this one!"
			fi
			;;
		*PARANOID*)
			# NOTE: "paranoid" is not the best word to
			# describe this situation.  Although most of
			# the matches this "feature" will find are
			# just configuration errors, it could also
			# easily indicate a DNS attack is in progress.
			#
			echo "$argv0: SMTP blocked from ${QUERY} because of insecure DNS."
			echo "WARNING: further RBL checks suspended until their DNS is fixed."
			if $VERBOSE; then
				host -A $QUERY
				host -A $addr
			fi
			continue
			;;
		*)
			OPTION=$($getline | awk -F: '{print $3}')
			if [ X"$OPTION" != X"ALLOW" ]; then
				echo "$argv0: ${QUERY} matched '${RBL} : ${OPTION}', but not an RBL match."
				$DEBUG && echo "$argv0: in '${file}' (line #${lineno})"
			fi
			continue
			;;
		esac
		$DEBUG && echo "$argv0: the RBL listing ${QUERY} is at ${inaddr}${RBLDOMAIN}." 1>&2

		# prime the local cache with txt, etc., as appropriate
		#
		case "$RBLDOMAIN" in
		.mr-out.imrss.org)
			host -t TXT 2.0.0.127${RBLDOMAIN} > /dev/null 2>&1
			;;
		.relays.orbs.org)
			host -t TXT ${inaddr}${RBLDOMAIN} > /dev/null 2>&1
			host -t TXT 2.0.0.127${RBLDOMAIN} > /dev/null 2>&1
			;;
 		.dssl.imrss.org)
			host -a ${inaddr}${RBLDOMAIN} > /dev/null 2>&1
			host -t TXT ${inaddr}${RBLDOMAIN} > /dev/null 2>&1
			host -t a ${inaddr}${RBLDOMAIN} > /dev/null 2>&1
			;;
		esac

		# this is sometimes kind of ugly but it presents the
		# information from the RBL as explicitly as possible
		#
		host -a ${inaddr}${RBLDOMAIN} 2>/dev/null

		if $QUERYDB ; then
			case $RBLDOMAIN in
			.mr-out.imrss.org)
				$VERBOSE && echo "$argv0: querying open relay info from IMRSS.ORG..."
				echo "action=query&address=$addr&Submit=Query+output+point+IP+address\n---\n" |
					lynx -post_data -dump http://www.imrss.org/cgi-bin/update.cgi
				echo ""
				;;
			.relays.orbs.org)
				$VERBOSE && echo "$argv0: querying open relay info ORBS.ORG..."
				lynx -dump "http://www.orbs.org/messagelookup.cgi?address=$addr"
				echo ""
				;;
		        .dssl.imrss.org)
				$VERBOSE && echo "$argv0: querying dial-up port info from IMRSS.ORG..."
				echo "action=/cgi-bin/dssl/query.cgi&ip=$addr&submit=Submit\n---\n" |
					lynx -post_data -dump http://www.imrss.org/cgi-bin/dssl/query.cgi |
					sed '/Please enter the IP address to query/d'
				echo ""
				;;
			.rbl.maps.vix.com)
				$VERBOSE && echo "$argv0: querying RBL info from MAPS.VIX.COM..."
				lynx -dump http://maps.vix.com/cgi-bin/lookup'?'$addr
				echo ""
				;;
			*)
				echo "$argv0: database for $RBLDOMAIN not available (except in DNS form)" 1>&2
				;;
			esac
		fi
	done
done

exit 0
