news.utdallas.edu!tamsun.tamu.edu!cs.utexas.edu!wupost!howland.reston.ans.net!agate!cogsci.Berkeley.EDU!muir Sun Mar 7 12:55:51 CST 1993 Article: 1434 of comp.lang.perl Xref: feenix.metronet.com comp.lang.perl:1434 Path: feenix.metronet.com!news.utdallas.edu!tamsun.tamu.edu!cs.utexas.edu!wupost!howland.reston.ans.net!agate!cogsci.Berkeley.EDU!muir From: muir@idiom.berkeley.ca.us (David Muir Sharnoff) Newsgroups: comp.lang.perl #Subject: late night doodle -- expn - expand/verify mail addresses Date: 7 Mar 1993 11:18:50 GMT Organization: University of California, Berkeley Lines: 338 Distribution: world Message-ID: <1nclmq$erj@agate.berkeley.edu> NNTP-Posting-Host: cogsci.berkeley.edu Originator: muir@cogsci.Berkeley.EDU I suppose there are other expn programs floating around. Probably even one in perl... I wanted to try writing some socket code in perl -- I never had before. So I started writing and before I knew it, I had a pretty good mailing list exploder. The total time investment in this program is about five hours so far. I tried to cover every case, but it is possible (read likely) that I missed someting. Let me know. As far as I know, this does a great job. -Dave P.S. If there are any updates to this, they will be available by ftp from idiom.berkeley.ca.us P.P.S. I know they style isn't great -- this was a five hour hack job. #!/bin/sh # shar: Shell Archiver (v1.22) # # Run the following text with /bin/sh to create: # expn # sed 's/^X//' << 'SHAR_EOF' > expn && X#!/usr/local/bin/perl X X# hardcoded constants X$AF_INET = 2; X$SOCK_STREAM = 1; X$sockaddr = 'S n a4 x8'; X X# X# This program traces mailing lists. For each address under X# consideration, it opens a SMTP connection to the system and X# executes the EXPN command. X# X# It will not always work because many SMTP daemons do not X# implement EXPN. Also, since many systems do not receive mail X# themselves, but rather us MX records to forward it, there X# isn't any easy way to verify the address. X# X# You get what you pay for -- this is free software. NO X# WARRENTEE. X# X# This program expects to be able to fork off 'nslookup' to X# resolve MX records. X# X# David Muir Sharnoff , 3/7/93 X# X X$port = 'smtp'; Xchop($hostname = `hostname`); X# regex for usernames X$u = "([-A-Za-z_.0-9+]+)"; X# regex for hostname X$h = $u; X# regex for user@hostname X$uah = "$u\@$h"; X# remember argv[0] X$av0 = $0; X X$0 = "$av0 - parsing args"; Xfor $a (@ARGV) { X if ($a =~ /^$uah$/) { X &expn($1,$2); X next; X } X if ($a =~ /^$u$/) { X &expn($1,$hostname); X next; X } X die "could not parse '$a'"; X} X X$0 = "$av0 - building local socket"; X($name,$aliases,$proto) = getprotobyname('tcp'); X($name,$aliases,$port) = getservbyname($port,'tcp') X unless $port =~ /^\d+/; X($name,$aliases,$type,$len,$thisaddr) = gethostbyname($hostname); X$this = pack($sockaddr, $AF_INET, 0, $thisaddr); X X XHOST: Xwhile (@hosts) { X $them = shift(@hosts); X @users = split(' ',$users{$them}); X delete $users{$them}; X ($curdom = $them) =~ s/^[^\.]+//; X X $0 = "$av0 - looking up $them"; X X if ($giveup{$them}) { X &giveup(); X next; X } X if ($mx{$them}) { X $0 = "$av0 - MX redirecting $them to ".$mx{$them}; X die "internal error: $them = ".$mx{$them}."\n" X if $them eq $mx{$them}; X for $u (@users) { X &expn("$u\@$them",$mx{$them}); X } X next HOST; X } else { X $0 = "$av0 - gethostbyname($them)"; X ($name,$aliases,$type,$len,$thataddr) = gethostbyname($them); X # if we can't get an A record, try for an MX record. X unless($thataddr) { X $0 = "$av0 - nslookup of $them"; X open(T,">/tmp/expn$$") || die "open > /tmp/expn$$: $!\n"; X print T "set querytype=MX\n"; X print T "$them\n"; X close(T); X $cpref = 1.0E12; X undef $nthem; X open(NSLOOKUP,"nslookup < /tmp/expn$$|") || die "open nslookup: $!"; X while() { X if (/mail exchanger = $h/) { X $nh = $1; X if (/preference = (\d+)/) { X $pref = $1; X if ($pref < $cpref) { X $nthem = $nh; X } X } X } X } X close(NSLOOKUP); X unlink("/tmp/expn$$"); X unless ($nthem) { X &giveup(); X next HOST; X } X ($name,$aliases,$type,$len,$thataddr) = gethostbyname($nthem); X unless ($thataddr) { X &giveup(); X next HOST; X } X print "MX($them) = $nthem\n"; X $mx{$them} = $nthem; X # redeploy the users X for $u (@users) { X &expn("$u\@$them",$nthem); X } X next HOST; X } X } X X $0 = "$av0 - socket to $them"; X $that = pack($sockaddr, $AF_INET, $port, $thataddr); X socket(S, $AF_INET, $SOCK_STREAM, $proto) X || die "socket: $!"; X $0 = "$av0 - bind to $them"; X bind(S, $this) X || die "bind $hostname,0: $!"; X $0 = "$av0 - connect to $them"; X unless (connect(S, $that)) { X print STDERR "Could not connect to $them: $!\n"; X &giveup(); X next HOST; X } X select(S); X $| = 1; X select(STDOUT); X X $0 = "$av0 - talking to $them"; X while() { X print; X last if /^\d+ /; X } X # it seems that saying helo can confuse things, so don't X# &ps("helo EXPN:$hostname"); X# while() { X# print; X# last if /^\d+ /; X# } X X # $localh is the domainless version of $them when X # $them was originally referred to w/o the domain X # this should be on a per-user basis... X # or does it matter at all? X $localh = $them; X $localh = $oldhost{$them} if $oldhost{$them}; X X USER: X while(@users) { X $u = shift(@users); X X if ($u =~ /(.*)\@(.*)/) { X ($u, $chost) = ($1, $2); X $0 = "$av0 - expanding $u\@$chost at $them"; X &ps("expn $u\@$chost"); X } else { X $chost = $them; X $0 = "$av0 - expanding $u\@$localh at $them"; X &ps("expn $u\@$localh"); X } X $gotit = 0; X while($s = ) { X if ($s =~ /^(\d+)/) { X if ($1 != 250 && $1 != 550) { X &giveup($u); X last USER; X } X } X $s =~ s/[\n\r]//g; X $0 = "$av0 - parsing $them: $s"; X print "$s\n"; X#print __LINE__."name = $name, name{$u\@$chost} = ".$name{"$u\@$chost"}."\n"; X $name = $name{"$u\@$chost"} X if (! $name && $name{"$u\@$chost"}); X if ($s =~ /^(\d+([- ]))(.*)\<(.*)\>$/) { X ($done,$name,$newaddr) = ($2,$3,$4); X &understand($name,$newaddr); X last if $done eq " "; X } elsif ($s =~ /^250([ -])($uah)$/) { X ($done,$newaddr) = ($1,$2,$3); X &understand("",$newaddr); X last if $done eq " "; X } elsif ($s =~ /^500/) { X &giveup($u); X last USER; X } elsif ($s =~ /^550/) { X push(@final,"$name <$u\@$chost> (USER UNKNOWN)"); X $gotit = 1; X } else { X warn "did not understand: '$s'\n"; X &giveup($u); X last USER; X } X last if $s =~ /^\d+ /; X } X unless($gotit) { X &giveup($u); X last USER; X } X } X $0 = "$av0 - sending 'quit' to $them"; X &ps("quit"); X while() { X print; X last if /^\d+ /; X } X close(S); X} X$0 = "$av0 - printing final results"; X#print "----------\n"; Xfor $f (sort @final) { X print "$f\n"; X} Xexit(0); Xsub giveup X{ X local($user) = @_; X # add back a user if we gave up in the middle X push(@users,$user) if $user; X # don't bother with this system anymore X unless ($giveup{$them}) { X $giveup{$them} = 1; X print STDERR "Will not be able to verify at $them\n"; X } X for $u (@users) { X $name = $name{"$u\@$them"}; X push(@final,"$name <$u\@$them> (UNVERIFIED)"); X } X} Xsub expn X{ X local($user,$host) = @_; X $host = &trhost($host); X X push(@hosts,$host) unless $users{$host}; X $users{$host} .= " $user"; X} Xsub trhost X{ X # treat foo.bar as an alias for Foo.BAR X local($host) = @_; X local($trhost) = $host; X $trhost =~ tr/A-Z/a-z/; X if ($trhost{$trhost}) { X $host = $trhost{$trhost}; X } else { X $trhost{$trhost} = $host; X } X $trhost{$trhost}; X} Xsub ps X{ X local($p) = @_; X print "$p\n"; X print S "$p\n"; X} Xsub understand X{ X local($name,$newaddr) = @_; X X # to deal with Fred X if ($newaddr =~ /^(\S+)\s*\(.*\)$/) { X ($name,$newaddr) = ($2,$1); X } X#print __LINE__."name = $name, name{$u\@$chost} = ".$name{"$u\@$chost"}."\n"; X $name{"$u\@$chost"} = $name X if $name; X $name = $name{"$u\@$chost"} X unless $name; X if ($newaddr =~ /^$uah$/) { X ($user, $host) = ($1, $2); X $oldhost = $host; X $host =~ s/^([^\.]+)$/$1$curdom/; X if ($host ne $oldhost) { X $oldhost{$host} = $oldhost; X#print __LINE__."name = $name, name{$u\@$chost} = ".$name{"$u\@$chost"}."\n"; X $name{"$u\@$host"} = $name X if $name; X } X if ($user eq $u && X (&trhost($host) eq &trhost($chost) X || &trhost($host) eq &trhost($localh))) { X push(@final,"$name <$u\@$host>"); X } else { X &expn($user,$host); X } X#print __LINE__."name{$user\@$host} = $name\n"; X $name{"$user\@$host"} = $name X if $name; X#print __LINE__."name{$user\@$host} = $name\n"; X } else { X#print __LINE__."name{$u\@$chost} = ".$name{"$u\@$chost"}."\n"; X push(@final,"$name <$newaddr\@$chost>"); X } X $gotit = 1; X} SHAR_EOF chmod 0755 expn || echo "restore of expn fails" exit 0 news.utdallas.edu!wupost!howland.reston.ans.net!agate!cogsci.Berkeley.EDU!muir Wed Mar 10 11:06:21 CST 1993 Article: 1493 of comp.lang.perl Xref: feenix.metronet.com comp.lang.perl:1493 Path: feenix.metronet.com!news.utdallas.edu!wupost!howland.reston.ans.net!agate!cogsci.Berkeley.EDU!muir From: David Muir Sharnoff Newsgroups: comp.lang.perl #Subject: Re: late night doodle -- expn - expand/verify mail addresses Date: 10 Mar 1993 10:57:11 GMT Organization: University of California, Berkeley Lines: 487 Message-ID: <1nkhi7$sp4@agate.berkeley.edu> References: <1nclmq$erj@agate.berkeley.edu> NNTP-Posting-Host: cogsci.berkeley.edu Originator: muir@cogsci.Berkeley.EDU In article merlyn@ora.com (Randal L. Schwartz) writes: >David> As far as I know, this does a great job. > >As far as I know, this does a fine job in fewer steps. :-) I hate to disagree, but your program doesn't do anything. So, while I admit it has fewer steps... > >################################################## >#!/usr/bin/perl > >require 'chat2.pl'; > >($who,$where) = @ARGV; > >&chat'open_port($where,25); > >&chat'expect(10,'^220.*\n',1) || die "No header"; >&chat'print("expn $who\n"); >1 while &chat'expect(10, > '^250-(.*[^\r])\r?\n','print "$1\n "; 1', > '^250 (.*[^\r])\r?\n','print "$1\n"; 0', > '^550.*\n','print "no such user\n"; 0', > TIMEOUT,'0' >); > >&chat'close(); > >################################################## Perhaps I should have been more clear about the mission of my program: it is to follow mail aliases. It follows them as far as it can. It starts with the system you initially point it at and keeps going until there are no address it can further expand. It can take a very long time to expand a large mailing list. Actually, Randal, I worte it in part to see if I needed to use chat2.pl to talk to SMTP or NNTP style daemons. Not needed at all :-) It would only save a dozen or so lines. Anyway, the one person who actaully bothered to figure out what it did, didn't like the volume of output... So, I cleaned it up a bit. There's now a very pretty -v (verbose) option and the default only prints the final list. It still hasn't had much testing, so let me know if it bombs. Thanks, -Dave P.S. Further updates (?) available from ftp@idiom.berkeley.ca.us #!/bin/sh # shar: Shell Archiver (v1.22) # # Run the following text with /bin/sh to create: # expn # sed 's/^X//' << 'SHAR_EOF' > expn && X#!/usr/local/bin/perl X X# Bugs... X# X# sometimes gives inappropriate names to addresses X X# hardcoded constants X$AF_INET = 2; X$SOCK_STREAM = 1; X$sockaddr = 'S n a4 x8'; X X# X# This program traces mailing lists. For each address under X# consideration, it opens a SMTP connection to the system and X# executes the EXPN command. X# X# It will not always work because many SMTP daemons do not X# implement EXPN. Also, since many systems do not receive mail X# themselves, but rather us MX records to forward it, there X# isn't any easy way to verify the address. X# X# You get what you pay for -- this is free software. NO X# WARRENTEE. X# X# This program expects to be able to fork off 'nslookup' to X# resolve MX records. It also uses 'hostname'... X# X# David Muir Sharnoff , 3/10/93 X# X# Options: X# X# -v verbose output, nice and pretty X# -w watch the conversations with the daemons X# -d lots of ugly debugging output X# X X X$port = 'smtp'; Xchop($hostname = `hostname`); X# remember argv[0] X$av0 = $0; Xselect(STDERR); X X$0 = "$av0 - parsing args"; Xfor $a (@ARGV) { X if ($a eq '-v') { X $verbose += 1; X $vd += 1; X next; X } X if ($a eq '-w') { X $watch += 1; X $vd += 1; X next; X } X if ($a eq '-d') { X $debug += 1; X next; X } X if ($a =~ /^-/) { X die "Usage: $0: [-v] [-w] [-d] user[@host] [user2[host2] ...]"; X } X &expn(&parse($a,$hostname,undef,1)); X} X X$0 = "$av0 - building local socket"; X($name,$aliases,$proto) = getprotobyname('tcp'); X($name,$aliases,$port) = getservbyname($port,'tcp') X unless $port =~ /^\d+/; X($name,$aliases,$type,$len,$thisaddr) = gethostbyname($hostname); X$this = pack($sockaddr, $AF_INET, 0, $thisaddr); X X XHOST: Xwhile (@hosts) { X $server = shift(@hosts); X @users = split(' ',$users{$server}); X delete $users{$server}; X X $0 = "$av0 - looking up $server"; X X if ($giveup{$server}) { X &giveup(); X next; X } X if (defined $mx{$server}) { X $0 = "$av0 - MX redirecting $server to ".$mx{$server}; X die "internal error: $server = ".$mx{$server}."\n" X if $server eq $mx{$server}; X for $u (@users) { X &expn($mx{$server},$u,$name{"$u *** $server"}); X } X next HOST; X } else { X $0 = "$av0 - gethostbyname($server)"; X ($name,$aliases,$type,$len,$thataddr) = gethostbyname($server); X # if we can't get an A record, try for an MX record. X unless($thataddr) { X $0 = "$av0 - nslookup of $server"; X open(T,">/tmp/expn$$") || die "open > /tmp/expn$$: $!\n"; X print T "set querytype=MX\n"; X print T "$server\n"; X close(T); X $cpref = 1.0E12; X undef $nserver; X open(NSLOOKUP,"nslookup < /tmp/expn$$ 2> /dev/null|") || die "open nslookup: $!"; X while() { X if (/mail exchanger = ([-A-Za-z_.0-9+]+)/) { X $nh = $1; X if (/preference = (\d+)/) { X $pref = $1; X if ($pref < $cpref) { X $nserver = $nh; X } X } X } X } X close(NSLOOKUP); X unlink("/tmp/expn$$"); X unless ($nserver) { X &giveup(); X next HOST; X } X ($name,$aliases,$type,$len,$thataddr) = gethostbyname($nserver); X unless ($thataddr) { X &giveup(); X next HOST; X } X print "MX($server) = $nserver\n" if $debug; X print "$server -> $nserver\n" if $vd && !$debug; X $mx{$server} = $nserver; X # redeploy the users X for $u (@users) { X &expn($nserver,$u,$name{"$u *** $server"}); X } X next HOST; X } X } X X $0 = "$av0 - socket to $server"; X $that = pack($sockaddr, $AF_INET, $port, $thataddr); X socket(S, $AF_INET, $SOCK_STREAM, $proto) X || die "socket: $!"; X $0 = "$av0 - bind to $server"; X bind(S, $this) X || die "bind $hostname,0: $!"; X $0 = "$av0 - connect to $server"; X unless (connect(S, $that)) { X print STDERR "Could not connect to $server: $!\n"; X &giveup(); X next HOST; X } X select((select(S),$| = 1)[0]); X X $0 = "$av0 - talking to $server"; X while() { X print if $watch; X last if /^\d+ /; X } X # it seems that saying helo can confuse things, so don't X# &ps("helo EXPN:$hostname"); X# while() { X# print; X# last if /^\d+ /; X# } X X USER: X while(@users) { X $u = shift(@users); X $oldname = $names{"$u *** $server"}; X X $0 = "$av0 - expanding $u [@$server]"; X X if ($verbose) { X local($se) = $server; X local($sp); X $se =~ s/(\W)/\\$1/g; X $sp = " (\@$server)" X if ($u !~ /$se/); X print "$u$sp ->\n"; X } X &ps("expn $u"); X while($s = ) { X if ($s =~ /^(\d+)/) { X if ($1 != 250 && $1 != 550) { X &giveup($u); X last USER; X } X } X $s =~ s/[\n\r]//g; X $0 = "$av0 - parsing $server: $s"; X print "$s\n" if $watch; X if ($s =~ /^250([ -])(.+)/) { X ($done,$addr) = ($1,$2); X ($newhost, $newaddr, $newname) = &parse($addr,$server,$oldname); X print "($newhost, $newaddr, $newname) = &parse($addr, $server, $oldname)\n" if $debug; X if (! $newhost) { X &verbose(&final($newaddr,$server,$newname)); X } else { X $newmxhost = &mx($newhost); X print "$newmxhost = &mx($newhost)\n" if $debug; X X $0 = "$av0 - parsing $newaddr [@$newmxhost]"; X if (&trhost($newmxhost) eq &trhost($server)) { X &verbose(&final($newaddr,$newmxhost,$newname)); X } else { X print "\t$newaddr\n" if $verbose; X &expn($newmxhost,$newaddr,$newname); X } X } X next USER if ($done eq " "); X next; X } X if ($s =~ /^550/) { X print &final($u,$server,$oldname,"USER UNKNOWN"); X next; X } X if ($s =~ /^500/) { X &giveup($u); X last USER; X } X warn "$u@$chost: did not understand: '$s'\n"; X &giveup($u); X } X } X $0 = "$av0 - sending 'quit' to $server"; X &ps("quit"); X while() { X print if $watch; X last if /^\d+ /; X } X close(S); X} X$0 = "$av0 - printing final results"; Xprint "----------\n" if $vd; Xselect(STDOUT); Xfor $f (sort @final) { X print "$f\n"; X} Xexit(0); Xsub giveup X{ X local($user) = @_; X # add back a user if we gave up in the middle X push(@users,$user) if $user; X # don't bother with this system anymore X unless ($giveup{$server}) { X $giveup{$server} = 1; X print STDERR "Giving up on $server\n"; X } X for $u (@users) { X $name = $name{"$u\@$server"}; X &final($u,$server,$name{"$u *** $server"},'UNVERIFIED'); X } X} Xsub parse X{ X local($newaddr,$context_host,$old_name,$parsing_args) = @_; X local(@names) = $old_name; X X local($urx) = "([-A-Za-z_.0-9+]+)"; X # X # first, separate out the address part. X # X X # X # [NAME] X # [NAME] <[(NAME)] ADDR X # ADDR [(NAME)] X # (NAME) ADDR X # [(NAME)] X # X if ($newaddr =~ /^\<(.*)\>$/) { X print "\n" if $debug; X $newaddr = &trim($1); X print "na = $newaddr\n" if $debug; X } X if ($newaddr =~ /^([^\<\>]*)\<([^\<\>]*)\>([^\<\>]*)$/) { X # address has a < > pair in it. X print "N:$1 N:$3\n" if $debug; X $newaddr = &trim($2); X push(@names, &trim($3,$1)); X print "na = $newaddr\n" if $debug; X } X if ($newaddr =~ /^([^\(\)]*)\(([^\(\)]*)\)([^\(\)]*)$/) { X # address has a ( ) pair in it. X print "A:$1 (N:$2) A:$3\n" if $debug; X push(@names,&trim($2)); X local($f,$l) = (&trim($1),&trim($3)); X if (($f && $l) || !($f || $l)) { X # address looks like: X # foo (bar) baz or (bar) X # not allowed! X print STDERR "Could not parse $newaddr\n" if $vd; X return(undef,$newaddr,&firstname(@names)); X } X $newaddr = $f if $f; X $newaddr = $l if $l; X print "newaddr now = $newaddr\n" if $debug; X } X # X # @foo:bar X # j%k@l X # a@b X # b!a X # a X # X if ($newaddr =~ /^\@$urx\:(.+)$/) { X print "(@:)" if $debug; X return (&domainify($1,$current_host),$newaddr,&firstname(@names)); X } X if ($newaddr =~ /^(.+)\@$urx$/) { X print "(@)" if $debug; X return (&domainify($2,$current_host),$newaddr,&firstname(@names)); X } X if ($parsing_args) { X if ($newaddr =~ /^$urx\!(.+)$/) { X return (&domainify($1,$current_host),$newaddr,&firstname(@names)); X } X if ($newaddr =~ /^$urx$/) { X return ($context_host,$newaddr,&firstname(@names)); X } X print STDERR "Could not parse $newaddr\n"; X } X print "(?)" if $debug; X return(undef,$newaddr,&firstname(@names)); X} Xsub trim X{ X local(@v) = @_; X local($v,@r); X for $v (@v) { X $v =~ s/^\s+//; X $v =~ s/\s+$//; X push(@r,$v) if ($v =~ /\S/); X } X return(@r); X} Xsub domainify X{ X local($h,$hd) = @_; X return $h if ($h =~ /\./); X $hd =~ s/^[^\.]+//; X $h =~ s/^([^\.]+)$/$1$hd/; X $h; X} Xsub firstname X{ X local(@names) = @_; X local($n); X while(@names) { X $n = shift(@names); X return $n if $n =~ /\S/; X } X return undef; X} Xsub expn X{ X local($host,$addr,$name) = @_; X if ($host) { X $host = &trhost($host); X X push(@hosts,$host) unless $users{$host}; X $users{$host} .= " $addr"; X $names{"$addr *** $host"} = $name; X } else { X &final($addr,'NONE',$name); X } X} Xsub trhost X{ X # treat foo.bar as an alias for Foo.BAR X local($host) = @_; X local($trhost) = $host; X $trhost =~ tr/A-Z/a-z/; X if ($trhost{$trhost}) { X $host = $trhost{$trhost}; X } else { X $trhost{$trhost} = $host; X } X $trhost{$trhost}; X} Xsub ps X{ X local($p) = @_; X print "$p\n" if $watch; X print S "$p\n"; X} Xsub mx X{ X local($h) = @_; X while (defined $mx{&trhost($h)}) { X $0 = "$av0 - mx expand $h"; X $h = $mx{&trhost($h)}; X } X $h; X} Xsub final X{ X local($addr,$host,$name,$error) = @_; X local($he) = $host; X $he =~ s/(\W)/\\$1/g; X X X if ($addr !~ /@/) { X $addr = "$addr@$host"; X } elsif ($addr !~ /$he/) { X $addr = "$addr[@$host]"; X } X $name = "$name " if $name; X $error = " $error" if $error; X push(@final,"$name<$addr>$error"); X "\t$name<$addr>$error\n"; X} Xsub verbose X{ X local(@tp) = @_; X print "@tp" if $verbose; X} SHAR_EOF chmod 0755 expn || echo "restore of expn fails" exit 0 .