Article 10162 of comp.lang.perl:
Xref: feenix.metronet.com comp.lang.perl:10162 comp.mail.sendmail:4507 alt.sources:2703 comp.unix.admin:9062
Path: feenix.metronet.com!news.ecn.bgu.edu!usenet.ins.cwru.edu!agate!overload.lbl.gov!lll-winken.llnl.gov!fastrac.llnl.gov!usenet.ee.pdx.edu!usenet.ee.pdx.edu!trent
From: trent@sirius.cs.pdx.edu (Trent A. Fisher)
Newsgroups: comp.lang.perl,comp.mail.sendmail,alt.sources,comp.unix.admin
Subject: Re: expn program for sendmail
Date: 26 Jan 1994 01:05:12 GMT
Organization: Portland State University, CS Dept.
Lines: 333
Distribution: world
Message-ID: <TRENT.94Jan25170514@sirius.cs.pdx.edu>
References: <CK7C7w.MFK@Colorado.EDU>
NNTP-Posting-Host: sirius.cs.pdx.edu
In-reply-to: Tom Christiansen's message of Tue, 25 Jan 1994 20:14:19 GMT
X-spook-bait: munitions Kennedy Khaddafi NSA Waco, Texas Ft. Bragg


Archive-name: chkaddr
Submitted-by: trent@cs.pdx.edu

In article <CK7C7w.MFK@Colorado.EDU> Tom Christiansen writes:
> Here's a brief program to expand remote aliases using the
> smtp daemon on the farside, e.g.:  [...]

Well, here's my program to do something similar, except mine will
recursively resolve each address until it hits non-forwarding login or
an error.  For example, running it on trent@ursula.ee.pdx.edu will give
you (indentation shows recursion level):
	trent@ursula.ee.pdx.edu
	  \trent
	  trent@cs.pdx.edu
	    "|/usr/local/mh/lib/slocal -user trent"

You need to have 'nslookup' in your path, to do MX record checking (I
haven't tested this code thouroughly.  Other than this, I have been
using this for a year or two, and, hopefully, have most of the kinks
worked out.

Send any suggestion, comments, fixes to trent@cs.pdx.edu.

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	chkaddr
# This archive created: Tue Jan 25 16:56:28 1994
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'chkaddr'" '(6009 characters)'
cat << \SATANOSCILATEMYMETALICSONATAS > 'chkaddr'
#!/usr/local/bin/perl
#
# Check a given (internet) e-mail address via smtp (vrfy command)
#
#  Copyright (C) 1991-4 Trent A. Fisher (trent@cs.pdx.edu)
#  With portions of code stolen from eay@psych.psy.uq.oz.au (Eric Young)
#
#  This program 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 1, or (at your option)
#  any later version.
#
#  This program 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 this program; if not, write to the Free Software
#  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
#
#  Usage:
#	chkaddr [-v] [-d n] addrs...
#	-v	lets you snoop on the smtp connection
#	-d n	maximum depth to search
#	adrrs are e-mail addresses, if there is no '@domain',
#	'@localhost' will be appended
#
#  Todo:
#	- do a HELO (need FQDN of hostname, how do we get that??)
#	- use EXPN instead of VRFY (both??)
#  	- implement some way of caching smtp connections, e.g. if
#	  three people in a mailing list have their mail forwarded to
#	  the same machine, this program will open an smtp connection
#	  three times.
#	- handle addresses which are not fully domained, i.e.
#		foo@bletch.mit.edu
#		  foo@blatz (should be blatz.mit.edu)
#	  (My systems don't do this, but some do)
#	- look up full names via finger?
#

require 'sys/socket.ph';
require 'sys/errno.ph';
require 'getopts.pl';

$snoop = 0;			# Watch smtp conversations
$MAXDEPTH = 17;			# maximum mail hops (to stop recursion)
$timeout = 30;

# get any MX records for a given host
# talk to nslookup to get the info
sub getmx
{
	local($host) = @_;
	local(@mxhosts);
	local($tmpfile)="/tmp/pmx$$z";

	# prepare the file to give to nslookup
	open(TMP, ">$tmpfile") || die "$tmpfile";
	print TMP "set type=MX\n$host\n";
	close TMP;

	# feed nslookup our input file...
	open(NS, "nslookup <$tmpfile |") || die "nslookup";
	while (<NS>)
	{
		if (/$host\s+preference\s+=\s+(\d+).*exchanger\s+=\s+([^\s]+)/)
		{
			next if $host eq $2;
			push(@mxhosts, $2);
			# do something with preference, someday??
		}
	}
	close(NS);
	unlink($tmpfile);

	return(@mxhosts);
}


sub startsmtp
{
	local($name)=@_;
	local($status);
	local($n,$aliases,$proto,$port,$type,$len,$thisaddr,$thataddr);
	local($this, $that, $sockaddr);

	$sockaddr='Sna4x8';
	chop($hostname=`hostname`);

	($n,$aliases,$proto)=getprotobyname('tcp');
	($n,$aliases,$port)=getservbyname('smtp','tcp');
	($n,$aliases,$type,$len,$thisaddr)=gethostbyname($hostname);
	($n,$aliases,$type,$len,$thataddr)=gethostbyname($name);
	if (!defined $thataddr)
	{
		# at this point we check for an MX record
		@mxhosts = &getmx($name);
		if (@mxhosts)
		{
			print "  "x$depth,
			      "Mail for $name is handled by @mxhosts\n";
		}
		else
		{
			print "  "x$depth,
			      "I can't locate $name, hopefully sendmail can\n";
		}
		return(0);
	}

	$this=pack($sockaddr,&AF_INET,0,$thisaddr);
	$that=pack($sockaddr,&AF_INET,$port,$thataddr);

	socket(SMTP,&PF_INET,&SOCK_STREAM,$proto) || die "socket: $!";
	bind(SMTP,$this) || die "bind: $!";
	$status = connect(SMTP,$that);
	if ($status == 0)
	{
		print "  "x$depth, "Host $name is ";
		if ($! == &ECONNREFUSED)
		{
			print "fascist\n";
		}
		elsif ($status == &ETIMEDOUT)
		{
			print "down?\n";
		}
		else
		{
			print "having problems: ", $!, "\n";
		}
		return(0);
	}

	select(SMTP); $|=1;
	select(STDIN); $|=1;
	select(STDOUT); $|=1;

	# get the header out of the way
	while (<SMTP>)
	{
		print if ($snoop);
		last if (/^220 /);
		if (/^[24]21 /)
		{
			print "Host $name doesn't want to talk to us :-(\n";
			alarm(0);
			return(0);
		}
	}

	# be polite and do a HELO ??
	# need to get our own FQDN

	return(1);
}

# when  sendmail 8 came out I found out I should actually be using expn.
sub vrfy_cmd
{
	local($name)=@_;
	local(@ret);

	print SMTP "EXPN $name\n";
	while(<SMTP>)
	{
		print if ($snoop);
		if (/^250([ -]).*<([^>]+)>/)
		{
			push(@ret, $2);
			last if $1 ne "-";
		}
		else
		{
			print;		# some sort of error
			last;
		}
	}
	return(@ret);
}

sub quitsmtp
{
	print SMTP "QUIT\n";
	$_=<SMTP>;
	print if ($snoop);
	print STDOUT "close bad: $_" unless (/^221 /);
}

sub timeout
{
	die "  "x($depth+1)."host $host won't talk to us.\n";
}
	
#
# get a list of e-mail addresses
#
sub vrfy
{
	local($name, $host) = @_;
	local(@ret);

	# set up the timeout and the connection
        $SIG{'ALRM'}='timeout';
	eval 'alarm($timeout); $r = &startsmtp($host);' ||
		print $@;
 	alarm(0);
	return() unless $r;

	@ret = &vrfy_cmd($name);
	do quitsmtp;
	return(@ret);
}

#
# this recursive routine will, given an e-mail address, find out what
# it resolves to (via smtp).
#
sub checkit
{
	local($name, $host, $depth) = @_;
	local(@addrs);

	# prevent infinite recursion
	if ($depth > $MAXDEPTH)
	{
		print "  "x($depth+1),
		      "This looks like a mail loop to me.\n";
		return;
	}

	@addrs = &vrfy($name, $host);
#	print "@addrs\n";

	foreach (@addrs)
	{
		print "  "x$depth, "$_\n";
		
		next if /^\\?[^@]+$/; # local address

		# some smtps return the same e-mail address
		# thus causing a loop
		if (/$name@$host/i)
		{
			print "  "x($depth+1),
			 "warning! twisted smtp daemon.\n";
			return;
		}

		if (/^([\w-]+)@([\w.-]+)$/)
		{
			do checkit($1, $2, $depth+1);
		}
		else
		{
			print "  "x($depth+1),
			 "I don't understand this address, I hope you do\n";
		}
	}
}

#------------------------------------------------------------------------
# main program
#

&Getopts("vd:");
$snoop    = $opt_v;
$MAXDEPTH = $opt_d if $opt_d;

foreach (@ARGV)
{
	if (/^([\w-]+)@([\w.-]+)$/)
	{
		print "$_\n";
		do checkit($1, $2, 1);
	}
	else			# must be a local address
	{
		print "$_@localhost\n";
		do checkit($_, "localhost", 1);
	}
}
SATANOSCILATEMYMETALICSONATAS
if test 6009 -ne "`wc -c < 'chkaddr'`"
then
	echo shar: "error transmitting 'chkaddr'" '(should have been 6009 characters)'
fi
chmod 755 'chkaddr'
exit 0
#	End of shell archive

--
 Trent A. Fisher, Systems Manager/Administrator/Programmer
 Portland State University				       trent@cs.pdx.edu
 Computer Science Dept.			Stop Software Monopolies!  Join the LPF
     "Don't ask me how it works, or I'll start to whimper." -- Arthur Dent



