home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / altsrcs / 3 / 3626 / archie < prev    next >
Encoding:
Text File  |  1991-07-15  |  14.8 KB  |  565 lines

  1. #!/usr/bin/perl                                         #-*-perl-*-
  2. #!/u3/thesis/clipper/bin/perl                            # -*-perl-*-
  3. # Copyright (C) Khun Yee Fung clipper@csd.uwo.ca 1991
  4. # You can do anything to this program except selling it for profit or
  5. # pretending you wrote it. The copyright notice must be preserved in all 
  6. # copies. Absolutely no warranty.
  7. # $Id: archie,v 2.5 1991/07/14 08:20:23 clipper Exp clipper $
  8. # Machines I have tried: Sun 3, MIPS 4.51, Sequent Symmetry Dynix.
  9. eval "exec perl -S $0 $*"
  10.     if $running_under_some_shell;
  11.  
  12. # To get system dependant sys/socket.h name and the domain server IP
  13. # if resolver libary is not built in.
  14. require 'archie.depend';
  15. require $socket;
  16. require 'resolver.pl';
  17. require 'newgetopt.pl';
  18.  
  19. # To get the options on the command line. Exaplanations in the code
  20. # handling them
  21. &NGetOpt('match=i', 'reg', 'exact', 'nocase', 'case', 'host=s',
  22.      'ffile=s', 'format=s', 'along', 'norc', 'syntax');
  23.  
  24. # Get the name of this program. The last name is the one.
  25. @prog = split('/', $0);
  26. $prog = $prog[$#prog];
  27.  
  28. # Usage string.
  29. $usage = 
  30. "Usage: $prog [options] word1 word2 ...
  31.   Where options are one or more of the following:
  32.   -case           Case sensitive
  33.   -nocase         Case insensitive
  34.   -exact          Exact match
  35.   -reg            Regular expression match
  36.   -match \#       Max hits
  37.   -host           Instead of quiche.cs.mcgill.ca
  38.   -ffile filename Use a format file
  39.   -format string  Specify a format string
  40.   -along          Print output along the way, instead of all at once at the end
  41.   -norc           Do not read .archierc file in home directory.
  42. ";
  43.  
  44. # Should have at least one query.
  45. if ($#ARGV < 0) {
  46.     print "Please specify at least one query.\n";
  47.     print $usage;
  48.     exit(255);
  49. }
  50. @string = @ARGV;
  51.  
  52. # For the conversion of date in the subroutine date.
  53. @month = ('', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
  54.       'Sep', 'Oct', 'Nov', 'Dec');
  55.  
  56. # The archie port number is 1525. Should probably try the privileged ports
  57. # (from 901 onwards or something) first.
  58. $port = 1525;
  59.  
  60. # The socketaddr structure. See /usr/include/sys/socket.h for the C 
  61. # version. The hostname is hard-wired. As there is a host option, this
  62. # is probably not very important. Hostcaps is for a strange entry format.
  63. # I still don't know exactly how the entries are specified.
  64. $sockaddr = 'S n a4 x8';
  65. $hostname = '132.206.2.3';
  66. $hostcaps = 'QUICHE.CS.MCGILL.CA';
  67.  
  68. # Defaults: maximum hit is 100. It does not mean there will be exactly
  69. # 100 entries returned, though. Expect a few entries more or less.
  70. # The default search option is case insensitive. Also, print all entries
  71. # at once at the end.
  72. $match = 100; $case = 'S'; $along = 0;
  73.  
  74. # To get the user name and user home path.
  75. @pw = getpwuid($<);
  76. $user = $pw[0];
  77. $userpath = $pw[7];
  78.  
  79. # The default format string. Can be overiden by the -format or -ffile 
  80. # options. Can also specify a default format string in ~/.archierc
  81. $format = "02%seq Host %host
  82. Last updated: %arc
  83.  
  84.     Location: %dir
  85.       %10type %mode %08size %date %name
  86.  
  87. ";
  88.  
  89. # Read ~/.archierc?
  90. if (!defined($opt_norc)) {
  91.     &parserc();
  92. }
  93.  
  94. # print entries when getting them?
  95. if (defined($opt_along)) {
  96.     $along = 1;
  97. }
  98.  
  99. # What is the number of hits wanted?
  100. if (defined($opt_match)) {
  101.     $match = $opt_match;
  102. }
  103.  
  104. # Read a format string from a file.
  105. if (defined($opt_ffile)) {
  106.     if (!open(FFILE, "$opt_ffile")) {
  107.     die "Can't open format file $opt_ffile\n";
  108.     }
  109.     $format = '';
  110.     while ($_ = <FFILE>) {
  111.     $format .= $_;
  112.     }
  113.     close(FFILE);
  114. }
  115.  
  116. # Read a format string on the command line.
  117. if (defined($opt_format)) {
  118.     $format = $opt_format;
  119. }
  120.  
  121. # Set search option to case sensitive.
  122. if (defined($opt_case)) {
  123.     $case = 'C';
  124. }
  125.  
  126. # Set search option to case insensitive.
  127. if (defined($opt_nocase)) {
  128.     $case = 'S';
  129. }
  130.  
  131. # set the search option to regular expression.
  132. if (defined($opt_reg)) {
  133.     $case = 'R';
  134. }
  135.  
  136. # set the search option to exact match
  137. if (defined($opt_exact)) {
  138.     $case = '=';
  139. }
  140.  
  141. # set a new archie host.
  142. if (defined($opt_host)) {
  143.     $hostname = $opt_host;
  144.     $hostcaps = y/a-z/A-Z/;
  145. }
  146.  
  147. # parse the format string,
  148. $format = &parseformat($format);
  149.  
  150. # This is for checking the format etc. Not for external use :-)
  151. if ($opt_syntax) {
  152.     print "Execution until here.\n";
  153.     exit(0);
  154. }
  155.  
  156. # Get the IP address of the archie server.
  157. if ($hostname =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
  158.   $thataddr = pack("CCCC", $1, $2, $3, $4);
  159. }
  160. elsif (!(($name, $aliases, $type, $len, $thataddr) = 
  161.      gethostbyname($hostname))) {
  162.     $thataddr = &resolver($hostname, $server) || die "No such host";
  163. }
  164. $them = pack($sockaddr, &AF_INET, $port, $thataddr);
  165. chop($thishost = `hostname`);
  166. ($name, $aliases, $type, $len, $thisaddr) = gethostbyname($thishost);
  167. $us = pack($sockaddr, &AF_INET, 0, $thisaddr);
  168.  
  169. # get and bind a socket.
  170. socket(DATA, &AF_INET, &SOCK_DGRAM, 0) || die "socket:$!\n";
  171. bind(DATA, $us) || die "bind: $!\n";
  172.  
  173. select(STDOUT); $| = 1;
  174.  
  175. # Get the list of matches.
  176. $hosts = &list($them, $user, @string);
  177.  
  178. # Print them.
  179. if (!$along) {
  180.     &output($hosts);
  181. }
  182. close(DATA);
  183.  
  184. # The subroutine list is the `meat' of the query.
  185. # It sends the query to the archie server host and parses the entries
  186. # returned by the server.
  187. sub list {
  188.     local($them, $user, @words) = @_;
  189.     local($answer, $ans, $timeout, $retries, $lines, @lines);
  190.     local($pktsnum, $pktseq, $seq, $rin, $timeleft, $rout, $nfound);
  191.     local($hdr_len, $id, $dum, $backoff, $ctlptr, $word, $kk);
  192.     local($first, $trailer, $header, $rdp, $index);
  193.     local($packet) = 0;
  194.     
  195.     $timeout = 4;
  196.     $retries = 3;
  197.  
  198.     # Construct the query packet.
  199.     @lines = ("VERSION 1\n", "AUTHENTICATOR UNAUTHENTICATED $user\n");
  200.     foreach $word (@words) {
  201.     push(@lines, "DIRECTORY ASCII ARCHIE/MATCH($match,0,$case)/$word\n");
  202.     push(@lines, "LIST ATTRIBUTES COMPONENTS \n");
  203.     }
  204.     $lines = join('', @lines);
  205.  
  206.     $first = 1;
  207.   retry: 
  208.     while ($first) {
  209.     $first = 0;
  210.     send(DATA, $lines, 0, $them);
  211.     $pktsnum = 0;
  212.     while (1) {
  213.         $seq = 0;
  214.  
  215.         # wait for a packet to come back.
  216.         $rin = '';
  217.         vec($rin, fileno(DATA), 1) = 1;
  218.         ($nfound, $timeleft) = select($rout = $rin, '', '', $timeout);
  219.         if (($timeleft == 0 || ord($rout) == 0) && ($retries-- > 0)) {
  220.         $timeout *= 2;
  221.         redo retry;
  222.         }
  223.  
  224.         # Read a packet from the server.
  225.         $ans = '';
  226.         if (!(recv(DATA, $ans, 10000, 0))) {
  227.         die "recv: Can't recv. Die.\n";
  228.         }
  229.         $packet++;
  230.  
  231.         # If the first byte is less than 20, then this is a old-fashioned
  232.         # packet. To be phased out later?
  233.         if (($hdr_len = ord(substr($ans, 0, 1))) < 20) {
  234.         # The header format is:
  235.         # CNNNNN
  236.         # The first byte is length, the second short integer the id,
  237.         # The third short integer the sequence number, then the number
  238.         # of packets on the way, a dummy field not useful for a client,
  239.         # and then the backoff time requested by the server.
  240.         $seq = 0;
  241.         ($hdr_len, $id, $seq, $pktsnum, $dum, $backoff) = 
  242.             unpack("Cn*", $ans);
  243.         if ($hdr_len < 5) {
  244.             $seq = $pktsnum = 1;
  245.         }
  246.         if ($hdr_len >= 11 && $backoff != 0) {
  247.             $timeout = $backoff;
  248.         }
  249.         next if ($seq == 0);
  250.         substr($ans, 0, $hdr_len) = '';
  251.         }
  252.         else {
  253.         # New format. Still have very vague about the format of
  254.         # this kind of packets. Could not find it in protocol.txt.
  255.         # Got the information from 
  256.         # lib/psrv/reply.c and lib/pfs/dirsend.c
  257.         # and a improved protocol.txt from bcn@cs.washington.edu.
  258.         $id = 0;
  259.         $rdp = ($hdr_len & 0xc0) >> 6;
  260.         $hdr_len = $hdr_len & 0x3F;
  261.         $header = substr($ans, 0, $hdr_len);
  262.         substr($ans, 0, $hdr_len) = '';
  263.         $index = index($ans, "\000");
  264.         $index++;
  265.         $trailer = substr($ans, $index);
  266.         substr($ans, $index) = '';
  267.         ($id, $seq, $kk, $dum, $backoff) = unpack("nnnnn", $trailer);
  268.         if ($kk) {
  269.             $pktsnum = $kk;
  270.         }
  271.         if ($backoff) {
  272.             $timeout = $backoff;
  273.         }
  274.         next if ($seq == 0);
  275.         # Get multi packet sequence and quantity.
  276.         if ($ans =~ /^MULTI-PACKET\s+(\d+)\s+OF\s+(\d+)/) {
  277.             # According to the source code, this is sent
  278.             # only as the last packet.
  279.             print "MULTI-PACKET OF is sent\n";
  280.             $seq = $1;
  281.             $pktsnum = $2;
  282.         }
  283.         elsif ($ans =~ /^MULTI-PACKET\s+(\d+)/) {
  284.             print "MULTI-PACKET is sent\n";
  285.             $seq = $1;
  286.             $pktsnum = 0;
  287.         }
  288.         else {
  289.             # output if the user wants to read the entries when
  290.             # still matching.
  291.             if ($along) {
  292.             &output($ans);
  293.             }
  294.             return($ans);
  295.         }
  296.         $index = index($ans, "\n");
  297.         $ans = substr($ans, $index + 1);
  298.         }
  299.         $pktseq++;
  300.         $retries = 3;
  301.         if ($along) {
  302.         &output($ans);
  303.         }
  304.         $answer .= $ans;
  305.         # The condition for getting more packets.
  306.         if ($pktsnum == 0 || $pktseq < $pktsnum) {
  307.         next;
  308.         }
  309.         last;
  310.     }
  311.     }
  312.     return($answer);
  313. }
  314.  
  315. # Print the entries in a packet.
  316. sub output {
  317.     local($list) = @_;
  318.     local(@lines, $dum, $arcmod, $lastmod, $modes, $host, $size, $dir);
  319.     local($name, @attr, @ainfo, $type);
  320.  
  321.     # split the lines in the packet first.
  322.     @lines = split(/\n/, $list);
  323.     $host = '';
  324.     while ($line = shift(@lines)) {
  325.     # If a LINK L line, then get the initial fields for the
  326.     # entry. Output the last entry if there is one.
  327.     if ($line =~ /^LINK L/) {
  328.         if ($host ne '') {
  329.         &write($host, $type, $dir, $size, $arcmod, $modes,
  330.                $lastmod, $name);
  331.         }
  332.         $type = $name = $host = $dir = '';
  333.         $size = $modes = $lastmod = $arcmod = '';
  334.         $#attr = $#ainfo = -1;
  335.         ($dum, $dum, $type, $name, $dum, $host, $dum, $dir, $dum, $dum) =
  336.         split(/ /, $line);
  337.     }
  338.     elsif ($line =~ /^LINK /) {
  339.         # What should I do if the response is LINK but not L?
  340.     }
  341.     elsif ($line =~ /^LINK-INFO/) {
  342.         # A LINK-INFO line. Get one attribute per line.
  343.         ($dum, $dum, $attr, $dum, @info) = split(/ /, $line);
  344.         if ($attr eq 'SIZE') {
  345.         $size = join(' ', @info);
  346.         }
  347.         elsif ($attr eq 'UNIX-MODES') {
  348.         $modes = join(' ', @info);
  349.         }
  350.         elsif ($attr eq 'ARC-MODTIME') {
  351.         $arcmod = join(' ', @info);
  352.         }
  353.         elsif ($attr eq 'LAST-MODIFIED') {
  354.         $lastmod = join(' ', @info);
  355.         }
  356.         else {
  357.         push(@attr, $attr);
  358.         push(@ainfo, join(' ', @info));
  359.         }
  360.     }
  361.     elsif ($line =~ /^VERSION-NOT-SUPPORTED TRY (\d+)-(\d+),(\d+)/) {
  362.         # What should I do if the version is wrong? Die?
  363.     }
  364.     elsif ($line =~ /^NOT-A-DIRECTORY/) {
  365.         # WHat about this?
  366.     }
  367.     elsif ($line =~ /^UNRESOLVED/) {
  368.     }
  369.     elsif ($line =~ /^FILTER/) {
  370.     }
  371.     elsif ($line =~ /^OBJECT-INFO/) {
  372.     }
  373.     elsif ($line =~ /^NONE-FOUND/) {
  374.     }
  375.     elsif ($line =~ /^SUCCESS/) {
  376.     }
  377.     elsif ($line =~ /^FORDWARDED/) {
  378.     }
  379.     elsif ($line =~ /^FAILURE/) {
  380.     }
  381.     elsif ($line =~ /^NOT-AUTHORIZED/) {
  382.     }
  383.     else {
  384.         # I basically don't know what to do if not LINK L and LINK-INFO.
  385.         # If you know, please tell me.
  386.     }
  387.     }
  388.     if ($host ne '') {
  389.     &write($host, $type, $dir, $size, $arcmod, $modes, $lastmod, $name);
  390.     }
  391. }
  392.  
  393. # Write the fields out on terminal using the format string.
  394. sub write {
  395.     local($host, $type, $dir, $size, $arcmod, $mode, $lastmod, $name) = @_;
  396.     local($seq, @path, $date, $path);
  397.     # Get the type of file.
  398.     if ($type eq 'DIRECTORY') {
  399.     $type = 'Directory';
  400.     }
  401.     else {
  402.     $type = 'File';
  403.     }
  404.  
  405.     # A strange format with the host being $hostcaps and the
  406.     # path being in this form: ARCHIE/HOST/path
  407.     if ($host eq $hostcaps) {
  408.     ($archie, $dum, $host, $dir) = 
  409.         ($dir =~ m|([^/]+)/([^/]+)/([^/]+)/(.*)$|);
  410.     $dir = '/' . $dir;
  411.     }
  412.  
  413.     # Convert the date string from 19910713123250Z to
  414.     # 1991 Jul 13 12:32:50 GMT
  415.     $date = &date($lastmod);
  416.     $pnum++;
  417.     $seq = $pnum;
  418.  
  419.     # print the entry. Die if something is wrong. Should I
  420.     # Log the output in a file so the effect is not wasted?
  421.     if ((eval "printf $format") eq '') {
  422.     die "A syntax error when printing the format string\n";
  423.     }
  424. }
  425.  
  426. # Convert a string.
  427. sub date {
  428.     local($date) = @_;
  429.     local($year, $month, $day, $hour, $min, $sec) =
  430.     (0, 1, 0, 0, 0, 0);
  431.     local($zone) = 'Z';
  432.  
  433.     ($year, $month, $day, $hour, $min, $sec, $zone) = 
  434.     ($date =~ /^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(.*)/);
  435.  
  436.     # A time zone Z is the same as GMT, right? I am not sure. Tell me
  437.     # if you know.
  438.     if ($zone eq 'Z') {
  439.     $zone = 'GMT';
  440.     }
  441.     return "$year $month[$month] $day $hour:$min:$sec $zone";
  442. }
  443.  
  444. # Parse the format string to convert it to a valid perl format
  445. # string.
  446. sub parseformat {
  447.     local($string) = @_;
  448.     local($nstring, $index, @plist);
  449.  
  450.     $string =~ s/\$/\\\$/g;
  451.     $string =~ s/\{/\\\{/g;
  452.     $string =~ s/\}/\\\}/g;
  453.     $nstring = '';
  454.     $#plist = -1;
  455.     while (($index = index($string, '%')) >= 0) {
  456.     $nstring .= substr($string, 0, $index);
  457.     substr($string, 0, $index) = '';
  458.     if (substr($string, 1, 1) eq '%') {
  459.         substr($string, 0, 2) = '';
  460.         $nstring .= '%%';
  461.     }
  462.     elsif ($string =~ /^\%(\d*)host/) {
  463.         # %12host means the width of the field is 12.
  464.         push(@plist, '$host');
  465.         $nstring .= "\%$1s";
  466.         substr($string, 0, length($1) + 5) = '';
  467.     }
  468.     elsif ($string =~ /^\%(\d*)dir/) {
  469.         push(@plist, '$dir');
  470.         $nstring .= "\%$1s";
  471.         substr($string, 0, length($1) + 4) = '';
  472.     }
  473.     elsif ($string =~ /^\%(\d*)mode/) {
  474.         push(@plist, '$mode');
  475.         $nstring .= "\%$1s";
  476.         substr($string, 0, length($1) + 5) = '';
  477.     }
  478.     elsif ($string =~ /^\%(\d*)date/) {
  479.         push(@plist, '$date');
  480.         $nstring .= "\%$1s";
  481.         substr($string, 0, length($1) + 5) = '';
  482.     }
  483.     elsif ($string =~ /^\%(\d*)arc/) {
  484.         push(@plist, '$arcmod');
  485.         $nstring .= "\%$1s";
  486.         substr($string, 0, length($1) + 4) = '';
  487.     }
  488.     elsif ($string =~ /^\%(\d*)seq/) {
  489.         push(@plist, '$seq');
  490.         $nstring .= "\%$1d";
  491.         substr($string, 0, length($1) + 4) = '';
  492.     }
  493.     elsif ($string =~ /^\%(\d*)size/) {
  494.         push(@plist, '$size');
  495.         $nstring .= "\%$1d";
  496.         substr($string, 0, length($1) + 5) = '';
  497.     }
  498.     elsif ($string =~ /^\%(\d*)name/) {
  499.         push(@plist, '$name');
  500.         $nstring .= "\%$1s";
  501.         substr($string, 0, length($1) + 5) = '';
  502.     }
  503.     elsif ($string =~ /^\%(\d*)type/) {
  504.         push(@plist, '$type');
  505.         $nstring .= "\%$1s";
  506.         substr($string, 0, length($1) + 5) = '';
  507.     }
  508.     else {
  509.         die sprintf("$prog: Format error. Unknown field: %s\n", $string);
  510.     }
  511.     }
  512.     $nstring .= $string;
  513.     $nstring = '"' . $nstring . '"';
  514.     return join(', ', $nstring, @plist);
  515. }
  516.  
  517. # Parse the startup file ~/.archierc
  518. # The format of the file is very simple:
  519. # command option
  520. # The format command must be the last one.
  521. sub parserc {
  522.     if (-e "$userpath/.archierc" && -r "$userpath/.archierc") {
  523.     open (RC, "$userpath/.archierc");
  524.     while (<RC>) {
  525.         chop;
  526.         if ($_ =~ /^\s*match\s+(\d+)\s*$/) {
  527.         $match = $1;
  528.         }
  529.         elsif ($_ =~/^\s*search\s+([a-z]+)\s*$/) {
  530.         if ($1 eq 'case') {
  531.             $case = 'C';
  532.         }
  533.         elsif ($1 eq 'nocase') {
  534.             $case = 'S';
  535.         }
  536.         elsif ($1 eq 'reg') {
  537.             $case = 'R';
  538.         }
  539.         elsif ($1 eq 'exact') {
  540.             $case = '=';
  541.         }
  542.         else {
  543.             print "$prog: $user/.archierc: unknown search option $1\n";
  544.         }
  545.         }
  546.         elsif ($_ =~ /^\s*host\s+(.+)\s*$/) {
  547.         $hostname = $1;
  548.         }
  549.         elsif ($_ =~ /^\s*format\s*$/) {
  550.         $format = '';
  551.         while (<RC>) {
  552.             $format .= $_;
  553.         }
  554.         return;
  555.         }
  556.         elsif ($_ =~ /^\s*$/ || $_ =~ /^\s*\#/) {
  557.         }
  558.         else {
  559.         print "$prog: Unknown option in $user/.archierc: $_\n";
  560.         }
  561.     }
  562.     close(RC);
  563.     }
  564. }
  565.