PageRenderTime 62ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/contrib/sendmail/contrib/expn.pl

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