expn.pl
上传用户:xu_441
上传日期:2007-01-04
资源大小:1640k
文件大小:36k
源码类别:

Email客户端

开发平台:

Unix_Linux

  1. #!/usr/bin/perl
  2. 'di ';
  3. 'ds 00 \"';
  4. 'ig 00 ';
  5. #
  6. #       THIS PROGRAM IS ITS OWN MANUAL PAGE.  INSTALL IN man & bin.
  7. #
  8. use 5.001;
  9. use IO::Socket;
  10. # system requirements:
  11. #  must have 'nslookup' and 'hostname' programs.
  12. # $OrigHeader: /home/muir/bin/RCS/expn,v 3.11 1997/09/10 08:14:02 muir Exp muir $
  13. # TODO:
  14. # less magic should apply to command-line addresses
  15. # less magic should apply to local addresses
  16. # add magic to deal with cross-domain cnames
  17. # disconnect & reconnect after 25 commands to the same sendmail 8.8.* host
  18. # Checklist: (hard addresses)
  19. # 250 Kimmo Suominen <"|/usr/local/mh/lib/slocal -user kim"@grendel.tac.nyc.ny.us>
  20. # harry@hofmann.cs.Berkeley.EDU -> harry@tenet (.berkeley.edu)  [dead]
  21. # bks@cs.berkeley.edu -> shiva.CS (.berkeley.edu)       [dead]
  22. # dan@tc.cornell.edu -> brown@tiberius (.tc.cornell.edu)
  23. #############################################################################
  24. #
  25. #  Copyright (c) 1993 David Muir Sharnoff
  26. #  All rights reserved.
  27. #
  28. #  Redistribution and use in source and binary forms, with or without
  29. #  modification, are permitted provided that the following conditions
  30. #  are met:
  31. #  1. Redistributions of source code must retain the above copyright
  32. #     notice, this list of conditions and the following disclaimer.
  33. #  2. Redistributions in binary form must reproduce the above copyright
  34. #     notice, this list of conditions and the following disclaimer in the
  35. #     documentation and/or other materials provided with the distribution.
  36. #  3. All advertising materials mentioning features or use of this software
  37. #     must display the following acknowledgement:
  38. #       This product includes software developed by the David Muir Sharnoff.
  39. #  4. The name of David Sharnoff may not be used to endorse or promote products
  40. #     derived from this software without specific prior written permission.
  41. #
  42. #  THIS SOFTWARE IS PROVIDED BY THE DAVID MUIR SHARNOFF ``AS IS'' AND
  43. #  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  44. #  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  45. #  ARE DISCLAIMED.  IN NO EVENT SHALL DAVID MUIR SHARNOFF BE LIABLE
  46. #  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  47. #  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  48. #  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  49. #  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  50. #  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  51. #  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  52. #  SUCH DAMAGE.
  53. #
  54. # This copyright notice derrived from material copyrighted by the Regents
  55. # of the University of California.
  56. #
  57. # Contributions accepted.
  58. #
  59. #############################################################################
  60. # overall structure:
  61. # in an effort to not trace each address individually, but rather
  62. # ask each server in turn a whole bunch of questions, addresses to
  63. # be expanded are queued up.
  64. #
  65. # This means that all accounting w.r.t. an address must be stored in
  66. # various arrays.  Generally these arrays are indexed by the
  67. # string "$addr *** $server" where $addr is the address to be
  68. # expanded "foo" or maybe "foo@bar" and $server is the hostname
  69. # of the SMTP server to contact.
  70. #
  71. # important global variables:
  72. #
  73. # @hosts : list of servers still to be contacted
  74. # $server : name of the current we are currently looking at
  75. # @users = $users{@hosts[0]} : addresses to expand at this server
  76. # $u = $users[0] : the current address being expanded
  77. # $names{"$users[0] *** $server"} : the 'name' associated with the address
  78. # $mxbacktrace{"$users[0] *** $server"} : record of mx expansion
  79. # $mx_secondary{$server} : other mx relays at the same priority
  80. # $domainify_fallback{"$users[0] *** $server"} : alternative names to try 
  81. # instead of $server if $server doesn't work
  82. # $temporary_redirect{"$users[0] *** $server"} : when trying alternates,
  83. # temporarily channel all tries along current path
  84. # $giveup{$server} : do not bother expanding addresses at $server
  85. # $verbose : -v
  86. # $watch : -w
  87. # $vw : -v or -w
  88. # $debug : -d
  89. # $valid : -a
  90. # $levels : -1
  91. # $S : the socket connection to $server
  92. $have_nslookup = 1; # we have the nslookup program
  93. $port = 'smtp';
  94. $av0 = $0;
  95. $ENV{'PATH'} .= ":/usr/etc" unless $ENV{'PATH'} =~ m,/usr/etc,;
  96. $ENV{'PATH'} .= ":/usr/ucb" unless $ENV{'PATH'} =~ m,/usr/ucb,;
  97. select(STDERR);
  98. $0 = "$av0 - running hostname";
  99. chop($name = `hostname || uname -n`);
  100. $0 = "$av0 - lookup host FQDN and IP addr";
  101. ($hostname,$aliases,$type,$len,$thisaddr) = gethostbyname($name);
  102. $0 = "$av0 - parsing args";
  103. $usage = "Usage: $av0 [-1avwd] user[@host] [user2[host2] ...]";
  104. for $a (@ARGV) {
  105. die $usage if $a eq "-";
  106. while ($a =~ s/^(-.*)([1avwd])/$1/) {
  107. eval '$'."flag_$2 += 1";
  108. }
  109. next if $a eq "-";
  110. die $usage if $a =~ /^-/;
  111. &expn(&parse($a,$hostname,undef,1));
  112. }
  113. $verbose = $flag_v;
  114. $watch = $flag_w;
  115. $vw = $flag_v + $flag_w;
  116. $debug = $flag_d;
  117. $valid = $flag_a;
  118. $levels = $flag_1;
  119. die $usage unless @hosts;
  120. if ($valid) {
  121. if ($valid == 1) {
  122. $validRequirement = 0.8;
  123. } elsif ($valid == 2) {
  124. $validRequirement = 1.0;
  125. } elsif ($valid == 3) {
  126. $validRequirement = 0.9;
  127. } else {
  128. $validRequirement = (1 - (1/($valid-3)));
  129. print "validRequirement = $validRequirementn" if $debug;
  130. }
  131. }
  132. HOST:
  133. while (@hosts) {
  134. $server = shift(@hosts);
  135. @users = split(' ',$users{$server});
  136. delete $users{$server};
  137. # is this server already known to be bad?
  138. $0 = "$av0 - looking up $server";
  139. if ($giveup{$server}) {
  140. &giveup('mx domainify',$giveup{$server});
  141. next;
  142. }
  143. # do we already have an mx record for this host?
  144. next HOST if &mxredirect($server,*users);
  145. # look it up, or try for an mx.
  146. $0 = "$av0 - gethostbyname($server)";
  147. ($name,$aliases,$type,$len,$thataddr) = gethostbyname($server);
  148. # if we can't get an A record, try for an MX record.
  149. unless($thataddr) {
  150. &mxlookup(1,$server,"$server: could not resolve name",*users);
  151. next HOST;
  152. }
  153. # get a connection, or look for an mx
  154. $0 = "$av0 - socket to $server";
  155. $S = new IO::Socket::INET (
  156. 'PeerAddr' => $server,
  157. 'PeerPort' => $port,
  158. 'Proto' => 'tcp');
  159. if (! $S || ($debug == 10 && $server =~ /relayd.UU.NET$/i)) {
  160. $0 = "$av0 - $server: could not connect: $!n";
  161. $emsg = $!;
  162. unless (&mxlookup(0,$server,"$server: could not connect: $!",*users)) {
  163. &giveup('mx',"$server: Could not connect: $emsg");
  164. }
  165. next HOST;
  166. }
  167. $S->autoflush(1);
  168. # read the greeting
  169. $0 = "$av0 - talking to $server";
  170. &alarm("greeting with $server",'');
  171. while(<$S>) {
  172. alarm(0);
  173. print if $watch;
  174. if (/^(d+)([- ])/) {
  175. if ($1 != 220) {
  176. $0 = "$av0 - bad numeric response from $server";
  177. &alarm("giving up after bad response from $server",'');
  178. &read_response($2,$watch);
  179. alarm(0);
  180. print STDERR "$server: NOT 220 greeting: $_"
  181. if ($debug || $vw);
  182. if (&mxlookup(0,$server,"$server: did not respond with a 220 greeting",*users)) {
  183. close($S);
  184. next HOST;
  185. }
  186. }
  187. last if ($2 eq " ");
  188. } else {
  189. $0 = "$av0 - bad response from $server";
  190. print STDERR "$server: NOT 220 greeting: $_"
  191. if ($debug || $vw);
  192. unless (&mxlookup(0,$server,"$server: did not respond with SMTP codes",*users)) {
  193. &giveup('',"$server: did not talk SMTP");
  194. }
  195. close($S);
  196. next HOST;
  197. }
  198. &alarm("greeting with $server",'');
  199. }
  200. alarm(0);
  201. # if this causes problems, remove it
  202. $0 = "$av0 - sending helo to $server";
  203. &alarm("sending helo to $server","");
  204. &ps("helo $hostname");
  205. while(<$S>) {
  206. print if $watch;
  207. last if /^d+ /;
  208. }
  209. alarm(0);
  210. # try the users, one by one
  211. USER:
  212. while(@users) {
  213. $u = shift(@users);
  214. $0 = "$av0 - expanding $u [@$server]";
  215. # do we already have a name for this user?
  216. $oldname = $names{"$u *** $server"};
  217. print &compact($u,$server)." ->n" if ($verbose && ! $valid);
  218. if ($valid) {
  219. #
  220. # when running with -a, we delay taking any action 
  221. # on the results of our query until we have looked
  222. # at the complete output.  @toFinal stores expansions
  223. # that will be final if we take them.  @toExpn stores
  224. # expnansions that are not final.  @isValid keeps
  225. # track of our ability to send mail to each of the
  226. # expansions.
  227. #
  228. @isValid = ();
  229. @toFinal = ();
  230. @toExpn = ();
  231. }
  232. # ($ecode,@expansion) = &expn_vrfy($u,$server);
  233. (@foo) = &expn_vrfy($u,$server);
  234. ($ecode,@expansion) = @foo;
  235. if ($ecode) {
  236. &giveup('',$ecode,$u);
  237. last USER;
  238. }
  239. for $s (@expansion) {
  240. $s =~ s/[nr]//g;
  241. $0 = "$av0 - parsing $server: $s";
  242. $skipwatch = $watch;
  243. if ($s =~ /^[25]51([- ]).*<(.+)>/) {
  244. print "$s" if $watch;
  245. print "(pretending 250$1<$2>)" if ($debug && $watch);
  246. print "n" if $watch;
  247. $s = "250$1<$2>";
  248. $skipwatch = 0;
  249. }
  250. if ($s =~ /^250([- ])(.+)/) {
  251. print "$sn" if $skipwatch;
  252. ($done,$addr) = ($1,$2);
  253. ($newhost, $newaddr, $newname) =  &parse($addr,$server,$oldname, $#expansion == 0);
  254. print "($newhost, $newaddr, $newname) = &parse($addr, $server, $oldname)n" if $debug;
  255. if (! $newhost) {
  256. # no expansion is possible w/o a new server to call
  257. if ($valid) {
  258. push(@isValid, &validAddr($newaddr));
  259. push(@toFinal,$newaddr,$server,$newname);
  260. } else {
  261. &verbose(&final($newaddr,$server,$newname));
  262. }
  263. } else {
  264. $newmxhost = &mx($newhost,$newaddr);
  265. print "$newmxhost = &mx($newhost)n" 
  266. if ($debug && $newhost ne $newmxhost);
  267. $0 = "$av0 - parsing $newaddr [@$newmxhost]";
  268. print "levels = $levels, level{$u *** $server} = ".$level{"$u *** $server"}."n" if ($debug > 1);
  269. # If the new server is the current one, 
  270. # it would have expanded things for us
  271. # if it could have.  Mx records must be
  272. # followed to compare server names.
  273. # We are also done if the recursion
  274. # count has been exceeded.
  275. if (&trhost($newmxhost) eq &trhost($server) || ($levels && $level{"$u *** $server"} >= $levels)) {
  276. if ($valid) {
  277. push(@isValid, &validAddr($newaddr));
  278. push(@toFinal,$newaddr,$newmxhost,$newname);
  279. } else {
  280. &verbose(&final($newaddr,$newmxhost,$newname));
  281. }
  282. } else {
  283. # more work to do...
  284. if ($valid) {
  285. push(@isValid, &validAddr($newaddr));
  286. push(@toExpn,$newmxhost,$newaddr,$newname,$level{"$u *** $server"});
  287. } else {
  288. &verbose(&expn($newmxhost,$newaddr,$newname,$level{"$u *** $server"}));
  289. }
  290. }
  291. }
  292. last if ($done eq " ");
  293. next;
  294. }
  295. # 550 is a known code...  Should the be
  296. # included in -a output?  Might be a bug
  297. # here.  Does it matter?  Can assume that
  298. # there won't be UNKNOWN USER responses 
  299. # mixed with valid users?
  300. if ($s =~ /^(550)([- ])/) {
  301. if ($valid) {
  302. print STDERR "@$server:$u ($oldname) USER UNKNOWNn";
  303. } else {
  304. &verbose(&final($u,$server,$oldname,"USER UNKNOWN"));
  305. }
  306. last if ($2 eq " ");
  307. next;
  308. # 553 is a known code...  
  309. if ($s =~ /^(553)([- ])/) {
  310. if ($valid) {
  311. print STDERR "@$server:$u ($oldname) USER AMBIGUOUSn";
  312. } else {
  313. &verbose(&final($u,$server,$oldname,"USER AMBIGUOUS"));
  314. }
  315. last if ($2 eq " ");
  316. next;
  317. # 252 is a known code...  
  318. if ($s =~ /^(252)([- ])/) {
  319. if ($valid) {
  320. print STDERR "@$server:$u ($oldname) REFUSED TO VRFYn";
  321. } else {
  322. &verbose(&final($u,$server,$oldname,"REFUSED TO VRFY"));
  323. }
  324. last if ($2 eq " ");
  325. next;
  326. &giveup('',"$server: did not grok '$s'",$u);
  327. last USER;
  328. }
  329. if ($valid) {
  330. #
  331. # now we decide if we are going to take these
  332. # expansions or roll them back.
  333. #
  334. $avgValid = &average(@isValid);
  335. print "avgValid = $avgValidn" if $debug;
  336. if ($avgValid >= $validRequirement) {
  337. print &compact($u,$server)." ->n" if $verbose;
  338. while (@toExpn) {
  339. &verbose(&expn(splice(@toExpn,0,4)));
  340. }
  341. while (@toFinal) {
  342. &verbose(&final(splice(@toFinal,0,3)));
  343. }
  344. } else {
  345. print "Tossing some valid to avoid invalid ".&compact($u,$server)."n" if ($avgValid > 0.0 && ($vw || $debug));
  346. print &compact($u,$server)." ->n" if $verbose;
  347. &verbose(&final($u,$server,$newname));
  348. }
  349. }
  350. }
  351. &alarm("sending 'quit' to $server",'');
  352. $0 = "$av0 - sending 'quit' to $server";
  353. &ps("quit");
  354. while(<$S>) {
  355. print if $watch;
  356. last if /^d+ /;
  357. }
  358. close($S);
  359. alarm(0);
  360. }
  361. $0 = "$av0 - printing final results";
  362. print "----------n" if $vw;
  363. select(STDOUT);
  364. for $f (sort @final) {
  365. print "$fn";
  366. }
  367. unlink("/tmp/expn$$");
  368. exit(0);
  369. # abandon all attempts deliver to $server
  370. # register the current addresses as the final ones
  371. sub giveup
  372. {
  373. local($redirect_okay,$reason,$user) = @_;
  374. local($us,@so,$nh,@remaining_users);
  375. local($pk,$file,$line);
  376. ($pk, $file, $line) = caller;
  377. $0 = "$av0 - giving up on $server: $reason";
  378. #
  379. # add back a user if we gave up in the middle
  380. #
  381. push(@users,$user) if $user;
  382. #
  383. # don't bother with this system anymore
  384. #
  385. unless ($giveup{$server}) {
  386. $giveup{$server} = $reason;
  387. print STDERR "$reasonn";
  388. }
  389. print "Giveup at $file:$line!!! redirect okay = $redirect_okay; $reasonn" if $debug;
  390. #
  391. # Wait!
  392. # Before giving up, see if there is a chance that
  393. # there is another host to redirect to!
  394. # (Kids, don't do this at home!  Hacking is a dangerous
  395. # crime and you could end up behind bars.)
  396. #
  397. for $u (@users) {
  398. if ($redirect_okay =~ /bmxb/) {
  399. next if &try_fallback('mx',$u,*server,
  400. *mx_secondary,
  401. *already_mx_fellback);
  402. }
  403. if ($redirect_okay =~ /bdomainifyb/) {
  404. next if &try_fallback('domainify',$u,*server,
  405. *domainify_fallback,
  406. *already_domainify_fellback);
  407. }
  408. push(@remaining_users,$u);
  409. }
  410. @users = @remaining_users;
  411. for $u (@users) {
  412. print &compact($u,$server)." ->n" if ($verbose && $valid && $u);
  413. &verbose(&final($u,$server,$names{"$u *** $server"},$reason));
  414. }
  415. }
  416. #
  417. # This routine is used only within &giveup.  It checks to
  418. # see if we really have to giveup or if there is a second
  419. # chance because we did something before that can be 
  420. # backtracked.
  421. #
  422. # %fallback{"$user *** $host"} tracks what is able to fallback
  423. # %fellback{"$user *** $host"} tracks what has fallen back
  424. #
  425. # If there is a valid backtrack, then queue up the new possibility
  426. #
  427. sub try_fallback
  428. {
  429. local($method,$user,*host,*fall_table,*fellback) = @_;
  430. local($us,$fallhost,$oldhost,$ft,$i);
  431. if ($debug > 8) {
  432. print "Fallback table $method:n";
  433. for $i (sort keys %fall_table) {
  434. print "t'$i'tt'$fall_table{$i}'n";
  435. }
  436. print "Fellback table $method:n";
  437. for $i (sort keys %fellback) {
  438. print "t'$i'tt'$fellback{$i}'n";
  439. }
  440. print "U: $user H: $hostn";
  441. }
  442. $us = "$user *** $host";
  443. if (defined $fellback{$us}) {
  444. #
  445. # Undo a previous fallback so that we can try again
  446. # Nested fallbacks are avoided because they could
  447. # lead to infinite loops
  448. #
  449. $fallhost = $fellback{$us};
  450. print "Already $method fell back from $us -> n" if $debug;
  451. $us = "$user *** $fallhost";
  452. $oldhost = $fallhost;
  453. } elsif (($method eq 'mx') && (defined $mxbacktrace{$us}) && (defined $mx_secondary{$mxbacktrace{$us}})) {
  454. print "Fallback an MX expansion $us -> n" if $debug;
  455. $oldhost = $mxbacktrace{$us};
  456. } else {
  457. print "Oldhost($host, $us) = " if $debug;
  458. $oldhost = $host;
  459. }
  460. print "$oldhostn" if $debug;
  461. if (((defined $fall_table{$us}) && ($ft = $us)) || ((defined $fall_table{$oldhost}) && ($ft = $oldhost))) {
  462. print "$method Fallback = ".$fall_table{$ft}."n" if $debug;
  463. local(@so,$newhost);
  464. @so = split(' ',$fall_table{$ft});
  465. $newhost = shift(@so);
  466. print "Falling back ($method) $us -> $newhost (from $oldhost)n" if $debug;
  467. if ($method eq 'mx') {
  468. if (! defined ($mxbacktrace{"$user *** $newhost"})) {
  469. if (defined $mxbacktrace{"$user *** $oldhost"}) {
  470. print "resetting oldhost $oldhost to the original: " if $debug;
  471. $oldhost = $mxbacktrace{"$user *** $oldhost"};
  472. print "$oldhostn" if $debug;
  473. }
  474. $mxbacktrace{"$user *** $newhost"} = $oldhost;
  475. print "mxbacktrace $user *** $newhost -> $oldhostn" if $debug;
  476. }
  477. $mx{&trhost($oldhost)} = $newhost;
  478. } else {
  479. $temporary_redirect{$us} = $newhost;
  480. }
  481. if (@so) {
  482. print "Can still $method  $us: @son" if $debug;
  483. $fall_table{$ft} = join(' ',@so);
  484. } else {
  485. print "No more fallbacks for $usn" if $debug;
  486. delete $fall_table{$ft};
  487. }
  488. if (defined $create_host_backtrack{$us}) {
  489. $create_host_backtrack{"$user *** $newhost"} 
  490. = $create_host_backtrack{$us};
  491. }
  492. $fellback{"$user *** $newhost"} = $oldhost;
  493. &expn($newhost,$user,$names{$us},$level{$us});
  494. return 1;
  495. }
  496. delete $temporary_redirect{$us};
  497. $host = $oldhost;
  498. return 0;
  499. }
  500. # return 1 if you could send mail to the address as is.
  501. sub validAddr
  502. {
  503. local($addr) = @_;
  504. $res = &do_validAddr($addr);
  505. print "validAddr($addr) = $resn" if $debug;
  506. $res;
  507. }
  508. sub do_validAddr
  509. {
  510. local($addr) = @_;
  511. local($urx) = "[-A-Za-z_.0-9+]+";
  512. # u
  513. return 0 if ($addr =~ /^\/);
  514. # ?@h
  515. return 1 if ($addr =~ /.@$urx$/);
  516. # @h:?
  517. return 1 if ($addr =~ /^@$urx:./);
  518. # h!u
  519. return 1 if ($addr =~ /^$urx!./);
  520. # u
  521. return 1 if ($addr =~ /^$urx$/);
  522. # ?
  523. print "validAddr($addr) = ???n" if $debug;
  524. return 0;
  525. }
  526. # Some systems use expn and vrfy interchangeably.  Some only
  527. # implement one or the other.  Some check expn against mailing
  528. # lists and vrfy against users.  It doesn't appear to be
  529. # consistent.
  530. #
  531. # So, what do we do?  We try everything!
  532. #
  533. #
  534. # Ranking of result codes: good: 250, 251/551, 252, 550, anything else
  535. #
  536. # Ranking of inputs: best: user@host.domain, okay: user
  537. #
  538. # Return value: $error_string, @responses_from_server
  539. sub expn_vrfy
  540. {
  541. local($u,$server) = @_;
  542. local(@c) = ('expn', 'vrfy');
  543. local(@try_u) = $u;
  544. local(@ret,$code);
  545. if (($u =~ /(.+)@(.+)/) && (&trhost($2) eq &trhost($server))) {
  546. push(@try_u,$1);
  547. }
  548. TRY:
  549. for $c (@c) {
  550. for $try_u (@try_u) {
  551. &alarm("${c}'ing $try_u on $server",'',$u);
  552. &ps("$c $try_u");
  553. alarm(0);
  554. $s = <$S>;
  555. if ($s eq '') {
  556. return "$server: lost connection";
  557. }
  558. if ($s !~ /^(d+)([- ])/) {
  559. return "$server: garbled reply to '$c $try_u'";
  560. }
  561. if ($1 == 250) {
  562. $code = 250;
  563. @ret = ("",$s);
  564. push(@ret,&read_response($2,$debug));
  565. return (@ret);
  566. if ($1 == 551 || $1 == 251) {
  567. $code = $1;
  568. @ret = ("",$s);
  569. push(@ret,&read_response($2,$debug));
  570. next;
  571. }
  572. if ($1 == 252 && ($code == 0 || $code == 550)) {
  573. $code = 252;
  574. @ret = ("",$s);
  575. push(@ret,&read_response($2,$watch));
  576. next;
  577. }
  578. if ($1 == 550 && $code == 0) {
  579. $code = 550;
  580. @ret = ("",$s);
  581. push(@ret,&read_response($2,$watch));
  582. next;
  583. }
  584. &read_response($2,$watch);
  585. }
  586. }
  587. return "$server: expn/vrfy not implemented" unless @ret;
  588. return @ret;
  589. }
  590. # sometimes the old parse routine (now parse2) didn't
  591. # reject funky addresses. 
  592. sub parse
  593. {
  594. local($oldaddr,$server,$oldname,$one_to_one) = @_;
  595. local($newhost, $newaddr, $newname, $um) =  &parse2($oldaddr,$server,$oldname,$one_to_one);
  596. if ($newaddr =~ m,^["/],) {
  597. return (undef, $oldaddr, $newname) if $valid;
  598. return (undef, $um, $newname);
  599. }
  600. return ($newhost, $newaddr, $newname);
  601. }
  602. # returns ($new_smtp_server,$new_address,$new_name)
  603. # given a response from a SMTP server ($newaddr), the 
  604. # current host ($server), the old "name" and a flag that
  605. # indicates if it is being called during the initial 
  606. # command line parsing ($parsing_args)
  607. sub parse2
  608. {
  609. local($newaddr,$context_host,$old_name,$parsing_args) = @_;
  610. local(@names) = $old_name;
  611. local($urx) = "[-A-Za-z_.0-9+]+";
  612. local($unmangle);
  613. #
  614. # first, separate out the address part.
  615. #
  616. #
  617. # [NAME] <ADDR [(NAME)]>
  618. # [NAME] <[(NAME)] ADDR
  619. # ADDR [(NAME)]
  620. # (NAME) ADDR
  621. # [(NAME)] <ADDR>
  622. #
  623. if ($newaddr =~ /^<(.*)>$/) {
  624. print "<A:$1>n" if $debug;
  625. ($newaddr) = &trim($1);
  626. print "na = $newaddrn" if $debug;
  627. }
  628. if ($newaddr =~ /^([^<>]*)<([^<>]*)>([^<>]*)$/) {
  629. # address has a < > pair in it.
  630. print "N:$1 <A:$2> N:$3n" if $debug;
  631. ($newaddr) = &trim($2);
  632. unshift(@names, &trim($3,$1));
  633. print "na = $newaddrn" if $debug;
  634. }
  635. if ($newaddr =~ /^([^()]*)(([^()]*))([^()]*)$/) {
  636. # address has a ( ) pair in it.
  637. print "A:$1 (N:$2) A:$3n" if $debug;
  638. unshift(@names,&trim($2));
  639. local($f,$l) = (&trim($1),&trim($3));
  640. if (($f && $l) || !($f || $l)) {
  641. # address looks like:
  642. # foo (bar) baz  or (bar)
  643. # not allowed!
  644. print STDERR "Could not parse $newaddrn" if $vw;
  645. return(undef,$newaddr,&firstname(@names));
  646. }
  647. $newaddr = $f if $f;
  648. $newaddr = $l if $l;
  649. print "newaddr now = $newaddrn" if $debug;
  650. }
  651. #
  652. # @foo:bar
  653. # j%k@l
  654. # a@b
  655. # b!a
  656. # a
  657. #
  658. $unmangle = $newaddr;
  659. if ($newaddr =~ /^@($urx):(.+)$/) {
  660. print "(@:)" if $debug;
  661. # this is a bit of a cheat, but it seems necessary
  662. return (&domainify($1,$context_host,$2),$2,&firstname(@names),$unmangle);
  663. }
  664. if ($newaddr =~ /^(.+)@($urx)$/) {
  665. print "(@)" if $debug;
  666. return (&domainify($2,$context_host,$newaddr),$newaddr,&firstname(@names),$unmangle);
  667. }
  668. if ($parsing_args) {
  669. if ($newaddr =~ /^($urx)!(.+)$/) {
  670. return (&domainify($1,$context_host,$newaddr),$newaddr,&firstname(@names),$unmangle);
  671. }
  672. if ($newaddr =~ /^($urx)$/) {
  673. return ($context_host,$newaddr,&firstname(@names),$unmangle);
  674. }
  675. print STDERR "Could not parse $newaddrn";
  676. }
  677. print "(?)" if $debug;
  678. return(undef,$newaddr,&firstname(@names),$unmangle);
  679. }
  680. # return $u (@$server) unless $u includes reference to $server
  681. sub compact
  682. {
  683. local($u, $server) = @_;
  684. local($se) = $server;
  685. local($sp);
  686. $se =~ s/(W)/\$1/g;
  687. $sp = " (@$server)";
  688. if ($u !~ /$se/i) {
  689. return "$u$sp";
  690. }
  691. return $u;
  692. }
  693. # remove empty (spaces don't count) members from an array
  694. sub trim
  695. {
  696. local(@v) = @_;
  697. local($v,@r);
  698. for $v (@v) {
  699. $v =~ s/^s+//;
  700. $v =~ s/s+$//;
  701. push(@r,$v) if ($v =~ /S/);
  702. }
  703. return(@r);
  704. }
  705. # using the host part of an address, and the server name, add the
  706. # servers' domain to the address if it doesn't already have a 
  707. # domain.  Since this sometimes fails, save a back reference so
  708. # it can be unrolled.
  709. sub domainify
  710. {
  711. local($host,$domain_host,$u) = @_;
  712. local($domain,$newhost);
  713. # cut of trailing dots 
  714. $host =~ s/.$//;
  715. $domain_host =~ s/.$//;
  716. if ($domain_host !~ /./) {
  717. #
  718. # domain host isn't, keep $host whatever it is
  719. #
  720. print "domainify($host,$domain_host) = $hostn" if $debug;
  721. return $host;
  722. }
  723. # There are several weird situtations that need to be 
  724. # accounted for.  They have to do with domain relay hosts.
  725. #
  726. # Examples: 
  727. # host server "right answer"
  728. #
  729. # shiva.cs cs.berkeley.edu shiva.cs.berkeley.edu
  730. # shiva cs.berkeley.edu shiva.cs.berekley.edu
  731. # cumulus reed.edu @reed.edu:cumulus.uucp
  732. #  tiberius tc.cornell.edu tiberius.tc.cornell.edu
  733. #
  734. # The first try must always be to cut the domain part out of 
  735. # the server and tack it onto the host.
  736. #
  737. # A reasonable second try is to tack the whole server part onto
  738. # the host and for each possible repeated element, eliminate 
  739. # just that part.
  740. #
  741. # These extra "guesses" get put into the %domainify_fallback
  742. # array.  They will be used to give addresses a second chance
  743. # in the &giveup routine
  744. #
  745. local(%fallback);
  746. local($long); 
  747. $long = "$host $domain_host";
  748. $long =~ tr/A-Z/a-z/;
  749. print "long = $longn" if $debug;
  750. if ($long =~ s/^([^ ]+.)([^ ]+) 2(.[^ ]+.[^ ]+)/$1$2$3/) {
  751. # matches shiva.cs cs.berkeley.edu and returns shiva.cs.berkeley.edu
  752. print "condensed fallback $host $domain_host -> $longn" if $debug;
  753. $fallback{$long} = 9;
  754. }
  755. local($fh);
  756. $fh = $domain_host;
  757. while ($fh =~ /./) {
  758. print "FALLBACK $host.$fh = 1n" if $debug > 7;
  759. $fallback{"$host.$fh"} = 1;
  760. $fh =~ s/^[^.]+.//;
  761. }
  762. $fallback{"$host.$domain_host"} = 2;
  763. ($domain = $domain_host) =~ s/^[^.]+//;
  764. $fallback{"$host$domain"} = 6
  765. if ($domain =~ /./);
  766. if ($host =~ /./) {
  767. #
  768. # Host is already okay, but let's look for multiple
  769. # interpretations
  770. #
  771. print "domainify($host,$domain_host) = $hostn" if $debug;
  772. delete $fallback{$host};
  773. $domainify_fallback{"$u *** $host"} = join(' ',sort {$fallback{$b} <=> $fallback{$a};} keys %fallback) if %fallback;
  774. return $host;
  775. }
  776. $domain = ".$domain_host"
  777. if ($domain !~ /..*./);
  778. $newhost = "$host$domain";
  779. $create_host_backtrack{"$u *** $newhost"} = $domain_host;
  780. print "domainify($host,$domain_host) = $newhostn" if $debug;
  781. delete $fallback{$newhost};
  782. $domainify_fallback{"$u *** $newhost"} = join(' ',sort {$fallback{$b} <=> $fallback{$a};} keys %fallback) if %fallback;
  783. if ($debug) {
  784. print "fallback = ";
  785. print $domainify_fallback{"$u *** $newhost"} 
  786. if defined($domainify_fallback{"$u *** $newhost"});
  787. print "n";
  788. }
  789. return $newhost;
  790. }
  791. # return the first non-empty element of an array
  792. sub firstname
  793. {
  794. local(@names) = @_;
  795. local($n);
  796. while(@names) {
  797. $n = shift(@names);
  798. return $n if $n =~ /S/;
  799. }
  800. return undef;
  801. }
  802. # queue up more addresses to expand
  803. sub expn
  804. {
  805. local($host,$addr,$name,$level) = @_;
  806. if ($host) {
  807. $host = &trhost($host);
  808. if (($debug > 3) || (defined $giveup{$host})) {
  809. unshift(@hosts,$host) unless $users{$host};
  810. } else {
  811. push(@hosts,$host) unless $users{$host};
  812. }
  813. $users{$host} .= " $addr";
  814. $names{"$addr *** $host"} = $name;
  815. $level{"$addr *** $host"} = $level + 1;
  816. print "expn($host,$addr,$name)n" if $debug;
  817. return "t$addrn";
  818. } else {
  819. return &final($addr,'NONE',$name);
  820. }
  821. }
  822. # compute the numerical average value of an array
  823. sub average
  824. {
  825. local(@e) = @_;
  826. return 0 unless @e;
  827. local($e,$sum);
  828. for $e (@e) {
  829. $sum += $e;
  830. }
  831. $sum / @e;
  832. }
  833. # print to the server (also to stdout, if -w)
  834. sub ps
  835. {
  836. local($p) = @_;
  837. print ">>> $pn" if $watch;
  838. print $S "$pn";
  839. }
  840. # return case-adjusted name for a host (for comparison purposes)
  841. sub trhost 
  842. {
  843. # treat foo.bar as an alias for Foo.BAR
  844. local($host) = @_;
  845. local($trhost) = $host;
  846. $trhost =~ tr/A-Z/a-z/;
  847. if ($trhost{$trhost}) {
  848. $host = $trhost{$trhost};
  849. } else {
  850. $trhost{$trhost} = $host;
  851. }
  852. $trhost{$trhost};
  853. }
  854. # re-queue users if an mx record dictates a redirect
  855. # don't allow a user to be redirected more than once
  856. sub mxredirect
  857. {
  858. local($server,*users) = @_;
  859. local($u,$nserver,@still_there);
  860. $nserver = &mx($server);
  861. if (&trhost($nserver) ne &trhost($server)) {
  862. $0 = "$av0 - mx redirect $server -> $nservern";
  863. for $u (@users) {
  864. if (defined $mxbacktrace{"$u *** $nserver"}) {
  865. push(@still_there,$u);
  866. } else {
  867. $mxbacktrace{"$u *** $nserver"} = $server;
  868. print "mxbacktrace{$u *** $nserver} = $servern"
  869. if ($debug > 1);
  870. &expn($nserver,$u,$names{"$u *** $server"});
  871. }
  872. }
  873. @users = @still_there;
  874. if (! @users) {
  875. return $nserver;
  876. } else {
  877. return undef;
  878. }
  879. }
  880. return undef;
  881. }
  882. # follow mx records, return a hostname
  883. # also follow temporary redirections comming from &domainify and
  884. # &mxlookup
  885. sub mx
  886. {
  887. local($h,$u) = @_;
  888. for (;;) {
  889. if (defined $mx{&trhost($h)} && $h ne $mx{&trhost($h)}) {
  890. $0 = "$av0 - mx expand $h";
  891. $h = $mx{&trhost($h)};
  892. return $h;
  893. }
  894. if ($u) {
  895. if (defined $temporary_redirect{"$u *** $h"}) {
  896. $0 = "$av0 - internal redirect $h";
  897. print "Temporary redirect taken $u *** $h -> " if $debug;
  898. $h = $temporary_redirect{"$u *** $h"};
  899. print "$hn" if $debug;
  900. next;
  901. }
  902. $htr = &trhost($h);
  903. if (defined $temporary_redirect{"$u *** $htr"}) {
  904. $0 = "$av0 - internal redirect $h";
  905. print "temporary redirect taken $u *** $h -> " if $debug;
  906. $h = $temporary_redirect{"$u *** $htr"};
  907. print "$hn" if $debug;
  908. next;
  909. }
  910. }
  911. return $h;
  912. }
  913. }
  914. # look up mx records with the name server.
  915. # re-queue expansion requests if possible
  916. # optionally give up on this host.
  917. sub mxlookup 
  918. {
  919. local($lastchance,$server,$giveup,*users) = @_;
  920. local(*T);
  921. local(*NSLOOKUP);
  922. local($nh, $pref,$cpref);
  923. local($o0) = $0;
  924. local($nserver);
  925. local($name,$aliases,$type,$len,$thataddr);
  926. local(%fallback);
  927. return 1 if &mxredirect($server,*users);
  928. if ((defined $mx{$server}) || (! $have_nslookup)) {
  929. return 0 unless $lastchance;
  930. &giveup('mx domainify',$giveup);
  931. return 0;
  932. }
  933. $0 = "$av0 - nslookup of $server";
  934. open(T,">/tmp/expn$$") || die "open > /tmp/expn$$: $!n";
  935. print T "set querytype=MXn";
  936. print T "$servern";
  937. close(T);
  938. $cpref = 1.0E12;
  939. undef $nserver;
  940. open(NSLOOKUP,"nslookup < /tmp/expn$$ 2>&1 |") || die "open nslookup: $!";
  941. while(<NSLOOKUP>) {
  942. print if ($debug > 2);
  943. if (/mail exchanger = ([-A-Za-z_.0-9+]+)/) {
  944. $nh = $1;
  945. if (/preference = (d+)/) {
  946. $pref = $1;
  947. if ($pref < $cpref) {
  948. $nserver = $nh;
  949. $cpref = $pref;
  950. } elsif ($pref) {
  951. $fallback{$pref} .= " $nh";
  952. }
  953. }
  954. }
  955. if (/Non-existent domain/) {
  956. #
  957. # These addresss are hosed.  Kaput!  Dead! 
  958. # However, if we created the address in the
  959. # first place then there is a chance of 
  960. # salvation.
  961. #
  962. 1 while(<NSLOOKUP>);
  963. close(NSLOOKUP);
  964. return 0 unless $lastchance;
  965. &giveup('domainify',"$server: Non-existent domain",undef,1);
  966. return 0;
  967. }
  968. }
  969. close(NSLOOKUP);
  970. unlink("/tmp/expn$$");
  971. unless ($nserver) {
  972. $0 = "$o0 - finished mxlookup";
  973. return 0 unless $lastchance;
  974. &giveup('mx domainify',"$server: Could not resolve address");
  975. return 0;
  976. }
  977. # provide fallbacks in case $nserver doesn't work out
  978. if (defined $fallback{$cpref}) {
  979. $mx_secondary{$server} = $fallback{$cpref};
  980. }
  981. $0 = "$av0 - gethostbyname($nserver)";
  982. ($name,$aliases,$type,$len,$thataddr) = gethostbyname($nserver);
  983. unless ($thataddr) {
  984. $0 = $o0;
  985. return 0 unless $lastchance;
  986. &giveup('mx domainify',"$nserver: could not resolve address");
  987. return 0;
  988. }
  989. print "MX($server) = $nservern" if $debug;
  990. print "$server -> $nservern" if $vw && !$debug;
  991. $mx{&trhost($server)} = $nserver;
  992. # redeploy the users
  993. unless (&mxredirect($server,*users)) {
  994. return 0 unless $lastchance;
  995. &giveup('mx domainify',"$nserver: only one level of mx redirect allowed");
  996. return 0;
  997. }
  998. $0 = "$o0 - finished mxlookup";
  999. return 1;
  1000. }
  1001. # if mx expansion did not help to resolve an address
  1002. # (ie: foo@bar became @baz:foo@bar, then undo the 
  1003. # expansion).
  1004. # this is only used by &final
  1005. sub mxunroll
  1006. {
  1007. local(*host,*addr) = @_;
  1008. local($r) = 0;
  1009. print "looking for mxbacktrace{$addr *** $host}n"
  1010. if ($debug > 1);
  1011. while (defined $mxbacktrace{"$addr *** $host"}) {
  1012. print "Unrolling MX expnasion: @$host:$addr -> " 
  1013. if ($debug || $verbose);
  1014. $host = $mxbacktrace{"$addr *** $host"};
  1015. print "@$host:$addrn" 
  1016. if ($debug || $verbose);
  1017. $r = 1;
  1018. }
  1019. return 1 if $r;
  1020. $addr = "@$host:$addr"
  1021. if ($host =~ /./);
  1022. return 0;
  1023. }
  1024. # register a completed expnasion.  Make the final address as 
  1025. # simple as possible.
  1026. sub final
  1027. {
  1028. local($addr,$host,$name,$error) = @_;
  1029. local($he);
  1030. local($hb,$hr);
  1031. local($au,$ah);
  1032. if ($error =~ /Non-existent domain/) {
  1033. # If we created the domain, then let's undo the
  1034. # damage...
  1035. #
  1036. if (defined $create_host_backtrack{"$addr *** $host"}) {
  1037. while (defined $create_host_backtrack{"$addr *** $host"}) {
  1038. print "Un&domainifying($host) = " if $debug;
  1039. $host = $create_host_backtrack{"$addr *** $host"};
  1040. print "$hostn" if $debug;
  1041. }
  1042. $error = "$host: could not locate";
  1043. } else {
  1044. # If we only want valid addresses, toss out
  1045. # bad host names.
  1046. #
  1047. if ($valid) {
  1048. print STDERR "@$host:$addr ($name) Non-existent domainn";
  1049. return "";
  1050. }
  1051. }
  1052. }
  1053. MXUNWIND: {
  1054. $0 = "$av0 - final parsing of @$host:$addr";
  1055. ($he = $host) =~ s/(W)/\$1/g;
  1056. if ($addr !~ /@/) {
  1057. # addr does not contain any host
  1058. $addr = "$addr@$host";
  1059. } elsif ($addr !~ /$he/i) {
  1060. # if host part really something else, use the something
  1061. # else.
  1062. if ($addr =~ m/(.*)@([^@]+)$/) {
  1063. ($au,$ah) = ($1,$2);
  1064. print "au = $au ah = $ahn" if $debug;
  1065. if (defined $temporary_redirect{"$addr *** $ah"}) {
  1066. $addr = "$au@".$temporary_redirect{"$addr *** $ah"};
  1067. print "Rewrite! to $addrn" if $debug;
  1068. next MXUNWIND;
  1069. }
  1070. }
  1071. # addr does not contain full host
  1072. if ($valid) {
  1073. if ($host =~ /^([^.]+)(..+)$/) {
  1074. # host part has a . in it - foo.bar
  1075. ($hb, $hr) = ($1, $2);
  1076. if ($addr =~ /@([^.@]+)$/ && ($1 eq $hb)) {
  1077. # addr part has not . 
  1078. # and matches beginning of
  1079. # host part -- tack on a 
  1080. # domain name.
  1081. $addr .= $hr;
  1082. } else {
  1083. &mxunroll(*host,*addr) 
  1084. && redo MXUNWIND;
  1085. }
  1086. } else {
  1087. &mxunroll(*host,*addr) 
  1088. && redo MXUNWIND;
  1089. }
  1090. } else {
  1091. $addr = "${addr}[@$host]"
  1092. if ($host =~ /./);
  1093. }
  1094. }
  1095. }
  1096. $name = "$name " if $name;
  1097. $error = " $error" if $error;
  1098. if ($valid) {
  1099. push(@final,"$name<$addr>");
  1100. } else {
  1101. push(@final,"$name<$addr>$error");
  1102. }
  1103. "t$name<$addr>$errorn";
  1104. }
  1105. sub alarm
  1106. {
  1107. local($alarm_action,$alarm_redirect,$alarm_user) = @_;
  1108. alarm(3600);
  1109. $SIG{ALRM} = 'handle_alarm';
  1110. }
  1111. # this involves one great big ugly hack.
  1112. # the "next HOST" unwinds the stack!
  1113. sub handle_alarm
  1114. {
  1115. &giveup($alarm_redirect,"Timed out during $alarm_action",$alarm_user);
  1116. next HOST;
  1117. }
  1118. # read the rest of the current smtp daemon's response (and toss it away)
  1119. sub read_response
  1120. {
  1121. local($done,$watch) = @_;
  1122. local(@resp);
  1123. print $s if $watch;
  1124. while(($done eq "-") && ($s = <$S>) && ($s =~ /^d+([- ])/)) {
  1125. print $s if $watch;
  1126. $done = $1;
  1127. push(@resp,$s);
  1128. }
  1129. return @resp;
  1130. }
  1131. # print args if verbose.  Return them in any case
  1132. sub verbose
  1133. {
  1134. local(@tp) = @_;
  1135. print "@tp" if $verbose;
  1136. }
  1137. # to pass perl -w:
  1138. @tp;
  1139. $flag_a;
  1140. $flag_d;
  1141. $flag_1;
  1142. %already_domainify_fellback;
  1143. %already_mx_fellback;
  1144. &handle_alarm;
  1145. ################### BEGIN PERL/TROFF TRANSITION 
  1146. .00 ;
  1147. 'di
  1148. .nr nl 0-1
  1149. .nr % 0
  1150. .\"'; __END__ 
  1151. ." ############## END PERL/TROFF TRANSITION
  1152. .TH EXPN 1 "March 11, 1993"
  1153. .AT 3
  1154. .SH NAME
  1155. expn - recursively expand mail aliases
  1156. .SH SYNOPSIS
  1157. .B expn
  1158. .RI [ -a ]
  1159. .RI [ -v ]
  1160. .RI [ -w ]
  1161. .RI [ -d ]
  1162. .RI [ -1 ]
  1163. .IR user [@ hostname ]
  1164. .RI [ user [@ hostname ]]...
  1165. .SH DESCRIPTION
  1166. .B expn
  1167. will use the SMTP
  1168. .B expn
  1169. and 
  1170. .B vrfy
  1171. commands to expand mail aliases.  
  1172. It will first look up the addresses you provide on the command line.
  1173. If those expand into addresses on other systems, it will 
  1174. connect to the other systems and expand again.  It will keep 
  1175. doing this until no further expansion is possible.
  1176. .SH OPTIONS
  1177. The default output of 
  1178. .B expn
  1179. can contain many lines which are not valid
  1180. email addresses.  With the 
  1181. .I -aa
  1182. flag, only expansions that result in legal addresses
  1183. are used.  Since many mailing lists have an illegal
  1184. address or two, the single
  1185. .IR -a ,
  1186. address, flag specifies that a few illegal addresses can
  1187. be mixed into the results.   More 
  1188. .I -a
  1189. flags vary the ratio.  Read the source to track down
  1190. the formula.  With the
  1191. .I -a
  1192. option, you should be able to construct a new mailing
  1193. list out of an existing one.
  1194. .LP
  1195. If you wish to limit the number of levels deep that 
  1196. .B expn
  1197. will recurse as it traces addresses, use the
  1198. .I -1
  1199. option.  For each 
  1200. .I -1
  1201. another level will be traversed.  So, 
  1202. .I -111
  1203. will traverse no more than three levels deep.
  1204. .LP
  1205. The normal mode of operation for
  1206. .B expn
  1207. is to do all of its work silently.
  1208. The following options make it more verbose.
  1209. It is not necessary to make it verbose to see what it is
  1210. doing because as it works, it changes its 
  1211. .BR argv [0]
  1212. variable to reflect its current activity.
  1213. To see how it is expanding things, the 
  1214. .IR -v ,
  1215. verbose, flag will cause 
  1216. .B expn 
  1217. to show each address before
  1218. and after translation as it works.
  1219. The 
  1220. .IR -w ,
  1221. watch, flag will cause
  1222. .B expn
  1223. to show you its conversations with the mail daemons.
  1224. Finally, the 
  1225. .IR -d ,
  1226. debug, flag will expose many of the inner workings so that
  1227. it is possible to eliminate bugs.
  1228. .SH ENVIRONMENT
  1229. No enviroment variables are used.
  1230. .SH FILES
  1231. .PD 0
  1232. .B /tmp/expn$$
  1233. .B temporary file used as input to 
  1234. .BR nslookup .
  1235. .SH SEE ALSO
  1236. .BR aliases (5), 
  1237. .BR sendmail (8),
  1238. .BR nslookup (8),
  1239. RFC 823, and RFC 1123.
  1240. .SH BUGS
  1241. Not all mail daemons will implement 
  1242. .B expn
  1243. or
  1244. .BR vrfy .
  1245. It is not possible to verify addresses that are served
  1246. by such daemons.
  1247. .LP
  1248. When attempting to connect to a system to verify an address,
  1249. .B expn
  1250. only tries one IP address.  Most mail daemons
  1251. will try harder.
  1252. .LP
  1253. It is assumed that you are running domain names and that 
  1254. the 
  1255. .BR nslookup (8) 
  1256. program is available.  If not, 
  1257. .B expn
  1258. will not be able to verify many addresses.  It will also pause
  1259. for a long time unless you change the code where it says
  1260. .I $have_nslookup = 1
  1261. to read
  1262. .I $have_nslookup = 
  1263. .IR 0 .
  1264. .LP
  1265. Lastly, 
  1266. .B expn
  1267. does not handle every valid address.  If you have an example,
  1268. please submit a bug report.
  1269. .SH CREDITS
  1270. In 1986 or so, Jon Broome wrote a program of the same name
  1271. that did about the same thing.  It has since suffered bit rot
  1272. and Jon Broome has dropped off the face of the earth!
  1273. (Jon, if you are out there, drop me a line)
  1274. .SH AVAILABILITY
  1275. The latest version of 
  1276. .B expn
  1277. is available through anonymous ftp at
  1278. .IR ftp://ftp.idiom.com/pub/muir-programs/expn .
  1279. .SH AUTHOR
  1280. .I David Muir Sharnoff    <muir@idiom.com>