6 # THIS PROGRAM IS ITS OWN MANUAL PAGE. INSTALL IN man & bin.
9 # hardcoded constants, should work fine for BSD-based systems
10 #require 'sys/socket.ph'; # perl 4
13 $SOCK_STREAM = &SOCK_STREAM;
15 # system requirements:
16 # must have 'nslookup' and 'hostname' programs.
18 # Header: /home/ezk/proj/amd/GIT/cvs/am-utils/scripts/expn.in,v 1.5 2002/07/11 14:28:20 ezk Exp
21 # less magic should apply to command-line addresses
22 # less magic should apply to local addresses
23 # add magic to deal with cross-domain cnames
25 # Checklist: (hard addresses)
26 # 250 Kimmo Suominen <"|/usr/local/mh/lib/slocal -user kim"@grendel.tac.nyc.ny.us>
27 # harry@hofmann.cs.Berkeley.EDU -> harry@tenet (.berkeley.edu) [dead]
28 # bks@cs.berkeley.edu -> shiva.CS (.berkeley.edu) [dead]
29 # dan@tc.cornell.edu -> brown@tiberius (.tc.cornell.edu)
31 #############################################################################
33 # Copyright (c) 1993 David Muir Sharnoff
34 # All rights reserved.
36 # Redistribution and use in source and binary forms, with or without
37 # modification, are permitted provided that the following conditions
39 # 1. Redistributions of source code must retain the above copyright
40 # notice, this list of conditions and the following disclaimer.
41 # 2. Redistributions in binary form must reproduce the above copyright
42 # notice, this list of conditions and the following disclaimer in the
43 # documentation and/or other materials provided with the distribution.
44 # 3. All advertising materials mentioning features or use of this software
45 # must display the following acknowledgement:
46 # This product includes software developed by the David Muir Sharnoff.
47 # 4. The name of David Sharnoff may not be used to endorse or promote products
48 # derived from this software without specific prior written permission.
50 # THIS SOFTWARE IS PROVIDED BY THE DAVID MUIR SHARNOFF ``AS IS'' AND
51 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
52 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
53 # ARE DISCLAIMED. IN NO EVENT SHALL DAVID MUIR SHARNOFF BE LIABLE
54 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
55 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
56 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
57 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
58 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
59 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
62 # This copyright notice derived from material copyrighted by the Regents
63 # of the University of California.
65 # Contributions accepted.
67 #############################################################################
70 # in an effort to not trace each address individually, but rather
71 # ask each server in turn a whole bunch of questions, addresses to
72 # be expanded are queued up.
74 # This means that all accounting w.r.t. an address must be stored in
75 # various arrays. Generally these arrays are indexed by the
76 # string "$addr *** $server" where $addr is the address to be
77 # expanded "foo" or maybe "foo@bar" and $server is the hostname
78 # of the SMTP server to contact.
81 # important global variables:
83 # @hosts : list of servers still to be contacted
84 # $server : name of the current we are currently looking at
85 # @users = $users{@hosts[0]} : addresses to expand at this server
86 # $u = $users[0] : the current address being expanded
87 # $names{"$users[0] *** $server"} : the 'name' associated with the address
88 # $mxbacktrace{"$users[0] *** $server"} : record of mx expansion
89 # $mx_secondary{$server} : other mx relays at the same priority
90 # $domainify_fallback{"$users[0] *** $server"} : alternative names to try
91 # instead of $server if $server doesn't work
92 # $temporary_redirect{"$users[0] *** $server"} : when trying alternates,
93 # temporarily channel all tries along current path
94 # $giveup{$server} : do not bother expanding addresses at $server
101 # S : the socket connection to $server
103 $have_nslookup = 1; # we have the nslookup program
106 $ENV{'PATH'} .= ":/usr/etc" unless $ENV{'PATH'} =~ m,/usr/etc,;
107 $ENV{'PATH'} .= ":/usr/ucb" unless $ENV{'PATH'} =~ m,/usr/ucb,;
110 $0 = "$av0 - running hostname";
111 chop($name = `hostname || uname -n`);
113 $0 = "$av0 - lookup host FQDN and IP addr";
114 ($hostname,$aliases,$type,$len,$thisaddr) = gethostbyname($name);
116 $0 = "$av0 - parsing args";
117 $usage = "Usage: $av0 [-1avwd] user[\@host] [user2[\@host2] ...]";
119 die $usage if $a eq "-";
120 while ($a =~ s/^(-.*)([1avwd])/$1/) {
121 eval '$'."flag_$2 += 1";
124 die $usage if $a =~ /^-/;
125 &expn(&parse($a,$hostname,undef,1));
129 $vw = $flag_v + $flag_w;
134 die $usage unless @hosts;
137 $validRequirement = 0.8;
138 } elsif ($valid == 2) {
139 $validRequirement = 1.0;
140 } elsif ($valid == 3) {
141 $validRequirement = 0.9;
143 $validRequirement = (1 - (1/($valid-3)));
144 print "validRequirement = $validRequirement\n" if $debug;
148 $0 = "$av0 - building local socket";
149 ($name,$aliases,$proto) = getprotobyname('tcp');
150 ($name,$aliases,$port) = getservbyname($port,'tcp')
151 unless $port =~ /^\d+/;
152 $this = sockaddr_in(0, $thisaddr);
156 $server = shift(@hosts);
157 @users = split(' ',$users{$server});
158 delete $users{$server};
160 # is this server already known to be bad?
161 $0 = "$av0 - looking up $server";
162 if ($giveup{$server}) {
163 &giveup('mx domainify',$giveup{$server});
167 # do we already have an mx record for this host?
168 next HOST if &mxredirect($server,*users);
170 # look it up, or try for an mx.
171 $0 = "$av0 - gethostbyname($server)";
173 ($name,$aliases,$type,$len,$thataddr) = gethostbyname($server);
174 # if we can't get an A record, try for an MX record.
176 &mxlookup(1,$server,"$server: could not resolve name",*users);
180 # get a connection, or look for an mx
181 $0 = "$av0 - socket to $server";
182 $that = sockaddr_in($port, $thataddr);
183 socket(S, &AF_INET, &SOCK_STREAM, $proto)
185 $0 = "$av0 - bind to $server";
187 || die "bind $hostname,0: $!";
188 $0 = "$av0 - connect to $server";
189 print "debug = $debug server = $server\n" if $debug > 8;
190 if (! connect(S, $that) || ($debug == 10 && $server =~ /relay\d.UU.NET$/i)) {
191 $0 = "$av0 - $server: could not connect: $!\n";
193 unless (&mxlookup(0,$server,"$server: could not connect: $!",*users)) {
194 &giveup('mx',"$server: Could not connect: $emsg");
198 select((select(S),$| = 1)[0]); # don't buffer output to S
201 $0 = "$av0 - talking to $server";
202 &alarm("greeting with $server",'');
206 if (/^(\d+)([- ])/) {
208 $0 = "$av0 - bad numeric response from $server";
209 &alarm("giving up after bad response from $server",'');
210 &read_response($2,$watch);
212 print STDERR "$server: NOT 220 greeting: $_"
214 if (&mxlookup(0,$server,"$server: did not respond with a 220 greeting",*users)) {
221 $0 = "$av0 - bad response from $server";
222 print STDERR "$server: NOT 220 greeting: $_"
224 unless (&mxlookup(0,$server,"$server: did not respond with SMTP codes",*users)) {
225 &giveup('',"$server: did not talk SMTP");
230 &alarm("greeting with $server",'');
234 # if this causes problems, remove it
235 $0 = "$av0 - sending helo to $server";
236 &alarm("sending helo to $server","");
237 &ps("helo $hostname");
244 # try the users, one by one
248 $0 = "$av0 - expanding $u [\@$server]";
250 # do we already have a name for this user?
251 $oldname = $names{"$u *** $server"};
253 print &compact($u,$server)." ->\n" if ($verbose && ! $valid);
256 # when running with -a, we delay taking any action
257 # on the results of our query until we have looked
258 # at the complete output. @toFinal stores expansions
259 # that will be final if we take them. @toExpn stores
260 # expansions that are not final. @isValid keeps
261 # track of our ability to send mail to each of the
269 # ($ecode,@expansion) = &expn_vrfy($u,$server);
270 (@foo) = &expn_vrfy($u,$server);
271 ($ecode,@expansion) = @foo;
273 &giveup('',$ecode,$u);
277 for $s (@expansion) {
279 $0 = "$av0 - parsing $server: $s";
283 if ($s =~ /^[25]51([- ]).*<(.+)>/) {
284 print "$s" if $watch;
285 print "(pretending 250$1<$2>)" if ($debug && $watch);
286 print "\n" if $watch;
291 if ($s =~ /^250([- ])(.+)/) {
292 print "$s\n" if $skipwatch;
293 ($done,$addr) = ($1,$2);
294 ($newhost, $newaddr, $newname) = &parse($addr,$server,$oldname, $#expansion == 0);
295 print "($newhost, $newaddr, $newname) = &parse($addr, $server, $oldname)\n" if $debug;
297 # no expansion is possible w/o a new server to call
299 push(@isValid, &validAddr($newaddr));
300 push(@toFinal,$newaddr,$server,$newname);
302 &verbose(&final($newaddr,$server,$newname));
305 $newmxhost = &mx($newhost,$newaddr);
306 print "$newmxhost = &mx($newhost)\n"
307 if ($debug && $newhost ne $newmxhost);
308 $0 = "$av0 - parsing $newaddr [@$newmxhost]";
309 print "levels = $levels, level{$u *** $server} = ".$level{"$u *** $server"}."\n" if ($debug > 1);
310 # If the new server is the current one,
311 # it would have expanded things for us
312 # if it could have. Mx records must be
313 # followed to compare server names.
314 # We are also done if the recursion
315 # count has been exceeded.
316 if (&trhost($newmxhost) eq &trhost($server) || ($levels && $level{"$u *** $server"} >= $levels)) {
318 push(@isValid, &validAddr($newaddr));
319 push(@toFinal,$newaddr,$newmxhost,$newname);
321 &verbose(&final($newaddr,$newmxhost,$newname));
326 push(@isValid, &validAddr($newaddr));
327 push(@toExpn,$newmxhost,$newaddr,$newname,$level{"$u *** $server"});
329 &verbose(&expn($newmxhost,$newaddr,$newname,$level{"$u *** $server"}));
333 last if ($done eq " ");
336 # 550 is a known code... Should the be
337 # included in -a output? Might be a bug
338 # here. Does it matter? Can assume that
339 # there won't be UNKNOWN USER responses
340 # mixed with valid users?
341 if ($s =~ /^(550)([- ])/) {
343 print STDERR "\@$server:$u ($oldname) USER UNKNOWN\n";
345 &verbose(&final($u,$server,$oldname,"USER UNKNOWN"));
350 # 553 is a known code...
351 if ($s =~ /^(553)([- ])/) {
353 print STDERR "\@$server:$u ($oldname) USER AMBIGUOUS\n";
355 &verbose(&final($u,$server,$oldname,"USER AMBIGUOUS"));
360 # 252 is a known code...
361 if ($s =~ /^(252)([- ])/) {
363 print STDERR "\@$server:$u ($oldname) REFUSED TO VRFY\n";
365 &verbose(&final($u,$server,$oldname,"REFUSED TO VRFY"));
370 &giveup('',"$server: did not grok '$s'",$u);
376 # now we decide if we are going to take these
377 # expansions or roll them back.
379 $avgValid = &average(@isValid);
380 print "avgValid = $avgValid\n" if $debug;
381 if ($avgValid >= $validRequirement) {
382 print &compact($u,$server)." ->\n" if $verbose;
384 &verbose(&expn(splice(@toExpn,0,4)));
387 &verbose(&final(splice(@toFinal,0,3)));
390 print "Tossing some valid to avoid invalid ".&compact($u,$server)."\n" if ($avgValid > 0.0 && ($vw || $debug));
391 print &compact($u,$server)." ->\n" if $verbose;
392 &verbose(&final($u,$server,$newname));
397 &alarm("sending 'quit' to $server",'');
398 $0 = "$av0 - sending 'quit' to $server";
408 $0 = "$av0 - printing final results";
409 print "----------\n" if $vw;
411 for $f (sort @final) {
414 unlink("/tmp/expn$$");
418 # abandon all attempts deliver to $server
419 # register the current addresses as the final ones
422 local($redirect_okay,$reason,$user) = @_;
423 local($us,@so,$nh,@remaining_users);
424 local($pk,$file,$line);
425 ($pk, $file, $line) = caller;
427 $0 = "$av0 - giving up on $server: $reason";
429 # add back a user if we gave up in the middle
431 push(@users,$user) if $user;
433 # don't bother with this system anymore
435 unless ($giveup{$server}) {
436 $giveup{$server} = $reason;
437 print STDERR "$reason\n";
439 print "Giveup at $file:$line!!! redirect okay = $redirect_okay; $reason\n" if $debug;
442 # Before giving up, see if there is a chance that
443 # there is another host to redirect to!
444 # (Kids, don't do this at home! Hacking is a dangerous
445 # crime and you could end up behind bars.)
448 if ($redirect_okay =~ /\bmx\b/) {
449 next if &try_fallback('mx',$u,*server,
451 *already_mx_fellback);
453 if ($redirect_okay =~ /\bdomainify\b/) {
454 next if &try_fallback('domainify',$u,*server,
456 *already_domainify_fellback);
458 push(@remaining_users,$u);
460 @users = @remaining_users;
462 print &compact($u,$server)." ->\n" if ($verbose && $valid && $u);
463 &verbose(&final($u,$server,$names{"$u *** $server"},$reason));
467 # This routine is used only within &giveup. It checks to
468 # see if we really have to giveup or if there is a second
469 # chance because we did something before that can be
472 # %fallback{"$user *** $host"} tracks what is able to fallback
473 # %fellback{"$user *** $host"} tracks what has fallen back
475 # If there is a valid backtrack, then queue up the new possibility
479 local($method,$user,*host,*fall_table,*fellback) = @_;
480 local($us,$fallhost,$oldhost,$ft,$i);
483 print "Fallback table $method:\n";
484 for $i (sort keys %fall_table) {
485 print "\t'$i'\t\t'$fall_table{$i}'\n";
487 print "Fellback table $method:\n";
488 for $i (sort keys %fellback) {
489 print "\t'$i'\t\t'$fellback{$i}'\n";
491 print "U: $user H: $host\n";
494 $us = "$user *** $host";
495 if (defined $fellback{$us}) {
497 # Undo a previous fallback so that we can try again
498 # Nested fallbacks are avoided because they could
499 # lead to infinite loops
501 $fallhost = $fellback{$us};
502 print "Already $method fell back from $us -> \n" if $debug;
503 $us = "$user *** $fallhost";
504 $oldhost = $fallhost;
505 } elsif (($method eq 'mx') && (defined $mxbacktrace{$us}) && (defined $mx_secondary{$mxbacktrace{$us}})) {
506 print "Fallback an MX expansion $us -> \n" if $debug;
507 $oldhost = $mxbacktrace{$us};
509 print "Oldhost($host, $us) = " if $debug;
512 print "$oldhost\n" if $debug;
513 if (((defined $fall_table{$us}) && ($ft = $us)) || ((defined $fall_table{$oldhost}) && ($ft = $oldhost))) {
514 print "$method Fallback = ".$fall_table{$ft}."\n" if $debug;
516 @so = split(' ',$fall_table{$ft});
517 $newhost = shift(@so);
518 print "Falling back ($method) $us -> $newhost (from $oldhost)\n" if $debug;
519 if ($method eq 'mx') {
520 if (! defined ($mxbacktrace{"$user *** $newhost"})) {
521 if (defined $mxbacktrace{"$user *** $oldhost"}) {
522 print "resetting oldhost $oldhost to the original: " if $debug;
523 $oldhost = $mxbacktrace{"$user *** $oldhost"};
524 print "$oldhost\n" if $debug;
526 $mxbacktrace{"$user *** $newhost"} = $oldhost;
527 print "mxbacktrace $user *** $newhost -> $oldhost\n" if $debug;
529 $mx{&trhost($oldhost)} = $newhost;
531 $temporary_redirect{$us} = $newhost;
534 print "Can still $method $us: @so\n" if $debug;
535 $fall_table{$ft} = join(' ',@so);
537 print "No more fallbacks for $us\n" if $debug;
538 delete $fall_table{$ft};
540 if (defined $create_host_backtrack{$us}) {
541 $create_host_backtrack{"$user *** $newhost"}
542 = $create_host_backtrack{$us};
544 $fellback{"$user *** $newhost"} = $oldhost;
545 &expn($newhost,$user,$names{$us},$level{$us});
548 delete $temporary_redirect{$us};
552 # return 1 if you could send mail to the address as is.
556 $res = &do_validAddr($addr);
557 print "validAddr($addr) = $res\n" if $debug;
563 local($urx) = "[-A-Za-z_.0-9+]+";
566 return 0 if ($addr =~ /^\\/);
568 return 1 if ($addr =~ /.\@$urx$/);
570 return 1 if ($addr =~ /^\@$urx\:./);
572 return 1 if ($addr =~ /^$urx!./);
574 return 1 if ($addr =~ /^$urx$/);
576 print "validAddr($addr) = ???\n" if $debug;
579 # Some systems use expn and vrfy interchangeably. Some only
580 # implement one or the other. Some check expn against mailing
581 # lists and vrfy against users. It doesn't appear to be
584 # So, what do we do? We try everything!
587 # Ranking of result codes: good: 250, 251/551, 252, 550, anything else
589 # Ranking of inputs: best: user@host.domain, okay: user
591 # Return value: $error_string, @responses_from_server
594 local($u,$server) = @_;
595 local(@c) = ('expn', 'vrfy');
599 if (($u =~ /(.+)@(.+)/) && (&trhost($2) eq &trhost($server))) {
605 for $try_u (@try_u) {
606 &alarm("${c}'ing $try_u on $server",'',$u);
611 return "$server: lost connection";
613 if ($s !~ /^(\d+)([- ])/) {
614 return "$server: garbled reply to '$c $try_u'";
619 push(@ret,&read_response($2,$debug));
622 if ($1 == 551 || $1 == 251) {
625 push(@ret,&read_response($2,$debug));
628 if ($1 == 252 && ($code == 0 || $code == 550)) {
631 push(@ret,&read_response($2,$watch));
634 if ($1 == 550 && $code == 0) {
637 push(@ret,&read_response($2,$watch));
640 &read_response($2,$watch);
643 return "$server: expn/vrfy not implemented" unless @ret;
646 # sometimes the old parse routine (now parse2) didn't
647 # reject funky addresses.
650 local($oldaddr,$server,$oldname,$one_to_one) = @_;
651 local($newhost, $newaddr, $newname, $um) = &parse2($oldaddr,$server,$oldname,$one_to_one);
652 if ($newaddr =~ m,^["/],) {
653 return (undef, $oldaddr, $newname) if $valid;
654 return (undef, $um, $newname);
656 return ($newhost, $newaddr, $newname);
659 # returns ($new_smtp_server,$new_address,$new_name)
660 # given a response from a SMTP server ($newaddr), the
661 # current host ($server), the old "name" and a flag that
662 # indicates if it is being called during the initial
663 # command line parsing ($parsing_args)
666 local($newaddr,$context_host,$old_name,$parsing_args) = @_;
667 local(@names) = $old_name;
668 local($urx) = "[-A-Za-z_.0-9+]+";
672 # first, separate out the address part.
676 # [NAME] <ADDR [(NAME)]>
677 # [NAME] <[(NAME)] ADDR
682 if ($newaddr =~ /^\<(.*)\>$/) {
683 print "<A:$1>\n" if $debug;
684 ($newaddr) = &trim($1);
685 print "na = $newaddr\n" if $debug;
687 if ($newaddr =~ /^([^\<\>]*)\<([^\<\>]*)\>([^\<\>]*)$/) {
688 # address has a < > pair in it.
689 print "N:$1 <A:$2> N:$3\n" if $debug;
690 ($newaddr) = &trim($2);
691 unshift(@names, &trim($3,$1));
692 print "na = $newaddr\n" if $debug;
694 if ($newaddr =~ /^([^\(\)]*)\(([^\(\)]*)\)([^\(\)]*)$/) {
695 # address has a ( ) pair in it.
696 print "A:$1 (N:$2) A:$3\n" if $debug;
697 unshift(@names,&trim($2));
698 local($f,$l) = (&trim($1),&trim($3));
699 if (($f && $l) || !($f || $l)) {
700 # address looks like:
701 # foo (bar) baz or (bar)
703 print STDERR "Could not parse $newaddr\n" if $vw;
704 return(undef,$newaddr,&firstname(@names));
708 print "newaddr now = $newaddr\n" if $debug;
717 $unmangle = $newaddr;
718 if ($newaddr =~ /^\@($urx)\:(.+)$/) {
719 print "(\@:)" if $debug;
720 # this is a bit of a cheat, but it seems necessary
721 return (&domainify($1,$context_host,$2),$2,&firstname(@names),$unmangle);
723 if ($newaddr =~ /^(.+)\@($urx)$/) {
724 print "(\@)" if $debug;
725 return (&domainify($2,$context_host,$newaddr),$newaddr,&firstname(@names),$unmangle);
728 if ($newaddr =~ /^($urx)\!(.+)$/) {
729 return (&domainify($1,$context_host,$newaddr),$newaddr,&firstname(@names),$unmangle);
731 if ($newaddr =~ /^($urx)$/) {
732 return ($context_host,$newaddr,&firstname(@names),$unmangle);
734 print STDERR "Could not parse $newaddr\n";
736 print "(?)" if $debug;
737 return(undef,$newaddr,&firstname(@names),$unmangle);
739 # return $u (@$server) unless $u includes reference to $server
742 local($u, $server) = @_;
743 local($se) = $server;
745 $se =~ s/(\W)/\\$1/g;
746 $sp = " (\@$server)";
752 # remove empty (spaces don't count) members from an array
760 push(@r,$v) if ($v =~ /\S/);
764 # using the host part of an address, and the server name, add the
765 # servers' domain to the address if it doesn't already have a
766 # domain. Since this sometimes fails, save a back reference so
767 # it can be unrolled.
770 local($host,$domain_host,$u) = @_;
771 local($domain,$newhost);
773 # cut of trailing dots
775 $domain_host =~ s/\.$//;
777 if ($domain_host !~ /\./) {
779 # domain host isn't, keep $host whatever it is
781 print "domainify($host,$domain_host) = $host\n" if $debug;
786 # There are several weird situations that need to be
787 # accounted for. They have to do with domain relay hosts.
790 # host server "right answer"
792 # shiva.cs cs.berkeley.edu shiva.cs.berkeley.edu
793 # shiva cs.berkeley.edu shiva.cs.berekley.edu
794 # cumulus reed.edu @reed.edu:cumulus.uucp
795 # tiberius tc.cornell.edu tiberius.tc.cornell.edu
797 # The first try must always be to cut the domain part out of
798 # the server and tack it onto the host.
800 # A reasonable second try is to tack the whole server part onto
801 # the host and for each possible repeated element, eliminate
804 # These extra "guesses" get put into the %domainify_fallback
805 # array. They will be used to give addresses a second chance
806 # in the &giveup routine
812 $long = "$host $domain_host";
813 $long =~ tr/A-Z/a-z/;
814 print "long = $long\n" if $debug;
815 if ($long =~ s/^([^ ]+\.)([^ ]+) \2(\.[^ ]+\.[^ ]+)/$1$2$3/) {
816 # matches shiva.cs cs.berkeley.edu and returns shiva.cs.berkeley.edu
817 print "condensed fallback $host $domain_host -> $long\n" if $debug;
818 $fallback{$long} = 9;
823 while ($fh =~ /\./) {
824 print "FALLBACK $host.$fh = 1\n" if $debug > 7;
825 $fallback{"$host.$fh"} = 1;
826 $fh =~ s/^[^\.]+\.//;
829 $fallback{"$host.$domain_host"} = 2;
831 ($domain = $domain_host) =~ s/^[^\.]+//;
832 $fallback{"$host$domain"} = 6
833 if ($domain =~ /\./);
837 # Host is already okay, but let's look for multiple
840 print "domainify($host,$domain_host) = $host\n" if $debug;
841 delete $fallback{$host};
842 $domainify_fallback{"$u *** $host"} = join(' ',sort {$fallback{$b} <=> $fallback{$a};} keys %fallback) if %fallback;
846 $domain = ".$domain_host"
847 if ($domain !~ /\..*\./);
848 $newhost = "$host$domain";
850 $create_host_backtrack{"$u *** $newhost"} = $domain_host;
851 print "domainify($host,$domain_host) = $newhost\n" if $debug;
852 delete $fallback{$newhost};
853 $domainify_fallback{"$u *** $newhost"} = join(' ',sort {$fallback{$b} <=> $fallback{$a};} keys %fallback) if %fallback;
856 print $domainify_fallback{"$u *** $newhost"}
857 if defined($domainify_fallback{"$u *** $newhost"});
862 # return the first non-empty element of an array
869 return $n if $n =~ /\S/;
873 # queue up more addresses to expand
876 local($host,$addr,$name,$level) = @_;
878 $host = &trhost($host);
880 if (($debug > 3) || (defined $giveup{$host})) {
881 unshift(@hosts,$host) unless $users{$host};
883 push(@hosts,$host) unless $users{$host};
885 $users{$host} .= " $addr";
886 $names{"$addr *** $host"} = $name;
887 $level{"$addr *** $host"} = $level + 1;
888 print "expn($host,$addr,$name)\n" if $debug;
891 return &final($addr,'NONE',$name);
894 # compute the numerical average value of an array
905 # print to the server (also to stdout, if -w)
909 print ">>> $p\n" if $watch;
912 # return case-adjusted name for a host (for comparison purposes)
915 # treat foo.bar as an alias for Foo.BAR
917 local($trhost) = $host;
918 $trhost =~ tr/A-Z/a-z/;
919 if ($trhost{$trhost}) {
920 $host = $trhost{$trhost};
922 $trhost{$trhost} = $host;
926 # re-queue users if an mx record dictates a redirect
927 # don't allow a user to be redirected more than once
930 local($server,*users) = @_;
931 local($u,$nserver,@still_there);
933 $nserver = &mx($server);
935 if (&trhost($nserver) ne &trhost($server)) {
936 $0 = "$av0 - mx redirect $server -> $nserver\n";
938 if (defined $mxbacktrace{"$u *** $nserver"}) {
939 push(@still_there,$u);
941 $mxbacktrace{"$u *** $nserver"} = $server;
942 print "mxbacktrace{$u *** $nserver} = $server\n"
944 &expn($nserver,$u,$names{"$u *** $server"});
947 @users = @still_there;
956 # follow mx records, return a hostname
957 # also follow temporary redirections coming from &domainify and
964 if (defined $mx{&trhost($h)} && $h ne $mx{&trhost($h)}) {
965 $0 = "$av0 - mx expand $h";
966 $h = $mx{&trhost($h)};
970 if (defined $temporary_redirect{"$u *** $h"}) {
971 $0 = "$av0 - internal redirect $h";
972 print "Temporary redirect taken $u *** $h -> " if $debug;
973 $h = $temporary_redirect{"$u *** $h"};
974 print "$h\n" if $debug;
978 if (defined $temporary_redirect{"$u *** $htr"}) {
979 $0 = "$av0 - internal redirect $h";
980 print "temporary redirect taken $u *** $h -> " if $debug;
981 $h = $temporary_redirect{"$u *** $htr"};
982 print "$h\n" if $debug;
989 # look up mx records with the name server.
990 # re-queue expansion requests if possible
991 # optionally give up on this host.
994 local($lastchance,$server,$giveup,*users) = @_;
997 local($nh, $pref,$cpref);
1000 local($name,$aliases,$type,$len,$thataddr);
1003 return 1 if &mxredirect($server,*users);
1005 if ((defined $mx{$server}) || (! $have_nslookup)) {
1006 return 0 unless $lastchance;
1007 &giveup('mx domainify',$giveup);
1011 $0 = "$av0 - nslookup of $server";
1012 open(T,">/tmp/expn$$") || die "open > /tmp/expn$$: $!\n";
1013 print T "set querytype=MX\n";
1014 print T "$server\n";
1018 open(NSLOOKUP,"nslookup < /tmp/expn$$ 2>&1 |") || die "open nslookup: $!";
1020 print if ($debug > 2);
1021 if (/mail exchanger = ([-A-Za-z_.0-9+]+)/) {
1023 if (/preference = (\d+)/) {
1025 if ($pref < $cpref) {
1029 $fallback{$pref} .= " $nh";
1033 if (/Non-existent domain/) {
1035 # These addresses are hosed. Kaput! Dead!
1036 # However, if we created the address in the
1037 # first place then there is a chance of
1040 1 while(<NSLOOKUP>);
1042 return 0 unless $lastchance;
1043 &giveup('domainify',"$server: Non-existent domain",undef,1);
1049 unlink("/tmp/expn$$");
1051 $0 = "$o0 - finished mxlookup";
1052 return 0 unless $lastchance;
1053 &giveup('mx domainify',"$server: Could not resolve address");
1057 # provide fallbacks in case $nserver doesn't work out
1058 if (defined $fallback{$cpref}) {
1059 $mx_secondary{$server} = $fallback{$cpref};
1062 $0 = "$av0 - gethostbyname($nserver)";
1063 ($name,$aliases,$type,$len,$thataddr) = gethostbyname($nserver);
1065 unless ($thataddr) {
1067 return 0 unless $lastchance;
1068 &giveup('mx domainify',"$nserver: could not resolve address");
1071 print "MX($server) = $nserver\n" if $debug;
1072 print "$server -> $nserver\n" if $vw && !$debug;
1073 $mx{&trhost($server)} = $nserver;
1074 # redeploy the users
1075 unless (&mxredirect($server,*users)) {
1076 return 0 unless $lastchance;
1077 &giveup('mx domainify',"$nserver: only one level of mx redirect allowed");
1080 $0 = "$o0 - finished mxlookup";
1083 # if mx expansion did not help to resolve an address
1084 # (ie: foo@bar became @baz:foo@bar, then undo the
1086 # this is only used by &final
1089 local(*host,*addr) = @_;
1091 print "looking for mxbacktrace{$addr *** $host}\n"
1093 while (defined $mxbacktrace{"$addr *** $host"}) {
1094 print "Unrolling MX expansion: \@$host:$addr -> "
1095 if ($debug || $verbose);
1096 $host = $mxbacktrace{"$addr *** $host"};
1097 print "\@$host:$addr\n"
1098 if ($debug || $verbose);
1102 $addr = "\@$host:$addr"
1106 # register a completed expansion. Make the final address as
1107 # simple as possible.
1110 local($addr,$host,$name,$error) = @_;
1115 if ($error =~ /Non-existent domain/) {
1117 # If we created the domain, then let's undo the
1120 if (defined $create_host_backtrack{"$addr *** $host"}) {
1121 while (defined $create_host_backtrack{"$addr *** $host"}) {
1122 print "Un&domainifying($host) = " if $debug;
1123 $host = $create_host_backtrack{"$addr *** $host"};
1124 print "$host\n" if $debug;
1126 $error = "$host: could not locate";
1129 # If we only want valid addresses, toss out
1133 print STDERR "\@$host:$addr ($name) Non-existent domain\n";
1140 $0 = "$av0 - final parsing of \@$host:$addr";
1141 ($he = $host) =~ s/(\W)/\\$1/g;
1143 # addr does not contain any host
1144 $addr = "$addr@$host";
1145 } elsif ($addr !~ /$he/i) {
1146 # if host part really something else, use the something
1148 if ($addr =~ m/(.*)\@([^\@]+)$/) {
1149 ($au,$ah) = ($1,$2);
1150 print "au = $au ah = $ah\n" if $debug;
1151 if (defined $temporary_redirect{"$addr *** $ah"}) {
1152 $addr = "$au\@".$temporary_redirect{"$addr *** $ah"};
1153 print "Rewrite! to $addr\n" if $debug;
1157 # addr does not contain full host
1159 if ($host =~ /^([^\.]+)(\..+)$/) {
1160 # host part has a . in it - foo.bar
1161 ($hb, $hr) = ($1, $2);
1162 if ($addr =~ /\@([^\.\@]+)$/ && ($1 eq $hb)) {
1163 # addr part has not .
1164 # and matches beginning of
1165 # host part -- tack on a
1169 &mxunroll(*host,*addr)
1173 &mxunroll(*host,*addr)
1177 $addr = "${addr}[\@$host]"
1182 $name = "$name " if $name;
1183 $error = " $error" if $error;
1185 push(@final,"$name<$addr>");
1187 push(@final,"$name<$addr>$error");
1189 "\t$name<$addr>$error\n";
1194 local($alarm_action,$alarm_redirect,$alarm_user) = @_;
1196 $SIG{ALRM} = 'handle_alarm';
1198 # this involves one great big ugly hack.
1199 # the "next HOST" unwinds the stack!
1202 &giveup($alarm_redirect,"Timed out during $alarm_action",$alarm_user);
1206 # read the rest of the current smtp daemon's response (and toss it away)
1209 local($done,$watch) = @_;
1212 while(($done eq "-") && ($s = <S>) && ($s =~ /^\d+([- ])/)) {
1219 # print args if verbose. Return them in any case
1223 print "@tp" if $verbose;
1230 %already_domainify_fellback;
1231 %already_mx_fellback;
1233 ################### BEGIN PERL/TROFF TRANSITION
1240 .\" ############## END PERL/TROFF TRANSITION
1241 .TH EXPN 1 "March 11, 1993"
1244 expn \- recursively expand mail aliases
1252 .IR user [@ hostname ]
1253 .RI [ user [@ hostname ]]...
1260 commands to expand mail aliases.
1261 It will first look up the addresses you provide on the command line.
1262 If those expand into addresses on other systems, it will
1263 connect to the other systems and expand again. It will keep
1264 doing this until no further expansion is possible.
1266 The default output of
1268 can contain many lines which are not valid
1269 email addresses. With the
1271 flag, only expansions that result in legal addresses
1272 are used. Since many mailing lists have an illegal
1273 address or two, the single
1275 address, flag specifies that a few illegal addresses can
1276 be mixed into the results. More
1278 flags vary the ratio. Read the source to track down
1279 the formula. With the
1281 option, you should be able to construct a new mailing
1282 list out of an existing one.
1284 If you wish to limit the number of levels deep that
1286 will recurse as it traces addresses, use the
1290 another level will be traversed. So,
1292 will traverse no more than three levels deep.
1294 The normal mode of operation for
1296 is to do all of its work silently.
1297 The following options make it more verbose.
1298 It is not necessary to make it verbose to see what it is
1299 doing because as it works, it changes its
1301 variable to reflect its current activity.
1302 To see how it is expanding things, the
1304 verbose, flag will cause
1306 to show each address before
1307 and after translation as it works.
1310 watch, flag will cause
1312 to show you its conversations with the mail daemons.
1315 debug, flag will expose many of the inner workings so that
1316 it is possible to eliminate bugs.
1318 No environment variables are used.
1322 .B temporary file used as input to
1328 RFC 823, and RFC 1123.
1330 Not all mail daemons will implement
1334 It is not possible to verify addresses that are served
1337 When attempting to connect to a system to verify an address,
1339 only tries one IP address. Most mail daemons
1342 It is assumed that you are running domain names and that
1345 program is available. If not,
1347 will not be able to verify many addresses. It will also pause
1348 for a long time unless you change the code where it says
1349 .I $have_nslookup = 1
1356 does not handle every valid address. If you have an example,
1357 please submit a bug report.
1359 In 1986 or so, Jon Broome wrote a program of the same name
1360 that did about the same thing. It has since suffered bit rot
1361 and Jon Broome has dropped off the face of the earth!
1362 (Jon, if you are out there, drop me a line)
1364 The latest version of
1366 is available through anonymous ftp at
1367 .IR ftp://ftp.idiom.com/pub/muir-programs/expn .
1369 .I David Muir Sharnoff\ \ \ \ <muir@idiom.com>