home *** CD-ROM | disk | FTP | other *** search
/ Otherware / Otherware_1_SB_Development.iso / mac / developm / source / rcpformp.sit / rcp.c next >
Encoding:
C/C++ Source or Header  |  1992-09-23  |  15.2 KB  |  689 lines

  1. /*
  2.  * RCP for MPW
  3.  * John Peterson, Taligent
  4.  *
  5.  * Based on Steve Falkenburg's Finger program,
  6.  * along with an ancient version of rcp I wrote for Aegis ages ago.
  7.  *
  8.  * Usage:
  9.  *    rcp [mac_vol:mac_dir:]mac_file unixhost@[unixdir]:unixdir[:unixfile] [-p][-d][-n][-u username]
  10.  * or
  11.  *    rcp unixhost@[unixdir:unixdir:]unixfile mac_vol:mac_dir[:mac_file] [-y][-p][-d][-n][-u username]
  12.  *
  13.  * The options are:
  14.  *    -d    Print debugging information
  15.  *    -n    Suppress newline conversion
  16.  *        (this is implied if copying a non 'TEXT' file from a Mac)
  17.  *    -p    Print a progress message
  18.  *    -u    Specify the Unix username to use
  19.  *        (this overrides the "UnixName" shell variable)
  20.  *    -y    Overwrite an existing Mac file
  21.  *        (only valid if copying from a remote host to the Mac)
  22.  *
  23.  * It tries to be somewhat clever about converting the file notation,
  24.  * so "kloo@:sub:xxx" copies files to a directory named "sub" in your 
  25.  * Unix home directory, but "kloo@tmp:xxx" specifies the file should be
  26.  * copied to "/tmp" on the remote machine.
  27.  *
  28.  * You can set the MPW variable "UnixName" to be the default username
  29.  * to use on the unix side.
  30.  * As with Unix RCP, you need to have an entry in the .rhosts file on
  31.  * the remote machine for rcp to work.
  32.  *
  33.  * To compile, put rcp.c in the same directory with the "Finger" source,
  34.  * and use the lines below.  The additional routines are described in
  35.  * Develop #6 (Spring '91).
  36.  *
  37. C rcp.c -o rcp.c.o
  38. link CvtAddr.c.o dnr.c.o TCPRoutines.c.o TCPHi.c.o rcp.c.o -o rcp -t 'MPST' -c 'MPS ' {CLibraries}StdCLib.o {Libraries}Runtime.o {Libraries}Interface.o {Libraries}ToolLibs.o
  39.  *
  40.  */
  41.  
  42. /*
  43.  *  BUGS
  44.  *
  45.  * "::blah" isn't translated to "../blah"
  46.  * Can't do recursive (directory) copies
  47.  */
  48.  
  49. #include "compat.h"
  50.  
  51. #ifdef PROTOS
  52. #include <Types.h>
  53. #include <Memory.h>
  54. #include <Packages.h>
  55. #include <Files.h>
  56. #include <CursorCtl.h>
  57. #include <Desk.h>        /* For SystemTask() */
  58. #endif
  59.  
  60. #include <string.h>
  61. #include <stdio.h>
  62. #include <stdlib.h>
  63.  
  64. #include "CvtAddr.h"
  65. #include "MacTCPCommonTypes.h"
  66. #include "TCPPB.h"
  67. #include "TCPHi.h"
  68.  
  69. /*
  70.  * Constants
  71.  */
  72. #define RCMD_PORT 514
  73. #define BUFSIZE 16384
  74. #define TIMEOUT 20
  75. #define NEWLINE 0x0a    /* Unix style newline */
  76.  
  77. /*
  78.  * Globals
  79.  */
  80.  
  81. char * source_host = NULL;
  82. char * source_file = NULL;
  83. char * dest_host = NULL;
  84. char * dest_file = NULL;
  85. char * unix_name = NULL;
  86.  
  87. char buffer[BUFSIZE];
  88.  
  89. Boolean progress_flag = false;
  90. Boolean debug_flag = false;
  91. Boolean stomp_dest_flag = false;
  92. Boolean cvt_nl_flag = true;
  93. Boolean user_nl_flag = false;
  94.  
  95. unsigned long tcp_stream = NULL;
  96.  
  97. /* For the TCP interface routines */
  98. Boolean gCancel = false;    /* this is set to true if the user cancels an operation */
  99.  
  100. /*
  101.  * Error reporting 
  102.  */
  103.  
  104. static void bomb( char * msg, char * arg )
  105. {    
  106.     if (tcp_stream)
  107.     {
  108.         (void) ReleaseStream( tcp_stream );
  109.     }
  110.     fprintf( stderr, "# %s%s\n", msg, arg ? arg : "" );
  111.     exit(-1L);
  112. }
  113.  
  114. static void check( OSErr err, char * msg )
  115. {
  116.     if (err)
  117.     {
  118.         fprintf( stderr, "# Error: %d while %s\n", err, msg );
  119.         if (tcp_stream)
  120.         {
  121.             (void) ReleaseStream( tcp_stream );
  122.         }
  123.         exit( err );
  124.     }
  125. }
  126.  
  127. /* this routine would normally be a callback for giving time to background apps */
  128.     
  129. Boolean GiveTime(short)
  130. {
  131.     SpinCursor(1);
  132.     return true;
  133. }
  134.  
  135. /*
  136.  * Argument munging
  137.  */
  138.  
  139. /* Allocate a duplicate copy of a string */
  140.  
  141. static char * strdup( char * s )
  142. {
  143.     char * scopy;
  144.     scopy = (char *) malloc( strlen( s ) + 1L );
  145.     if (! scopy)
  146.         bomb( "Heap space exhausted", NULL );
  147.     strcpy( scopy, s );
  148.     return scopy;
  149. }
  150.  
  151. /* Get the leaf (filename) component of a pathname */
  152.  
  153. static char * getleaf( char * fname )
  154. {
  155.     char * p;
  156.     
  157.     /* Look for leaf component of filename */
  158.     p = &fname[strlen( fname ) - 1];
  159.     while ((p > fname) && (*p != '/') && (*p != ':'))
  160.         p--;
  161.     if ((*p == '/') || (*p == ':')) p++;
  162.     
  163.     return p;
  164. }
  165.  
  166. /* Convert Unix newlines to Mac and vice-versa */
  167.  
  168. static void convert_nl( char * buf, int length )
  169. {
  170.     register int i;
  171.     
  172.     if (cvt_nl_flag)
  173.         for (i = 0; i < length; i++)
  174.             if (buf[i] == '\n')
  175.                 buf[i] = NEWLINE;
  176.             else
  177.             {
  178.                 if (buf[i] == NEWLINE)
  179.                     buf[i] = '\n';
  180.             }
  181. }
  182.  
  183. /* Convert Mac ':' directory marks to unix '/' */
  184.  
  185. static char * unixify( char * fname )
  186. {
  187.     register char * p = fname;
  188.     Boolean macstyle = false;
  189.     
  190.     while (*p)
  191.     {
  192.         if (*p == ':') 
  193.         {
  194.             macstyle = true;
  195.             *p = '/';
  196.         }
  197.         p++;
  198.     }
  199.         
  200.     /* On the mac, a leading ':' means a RELATIVE directory.
  201.      * On unix, a leading '/' means an ABSOLUTE directory.
  202.      * Still a bug: "::" should turn into "../"
  203.      */
  204.     if (macstyle)
  205.     {
  206.         if (*fname == '/')
  207.         {
  208.             p = fname;
  209.             p++;
  210.             p = strdup( p );
  211.         }
  212.         else
  213.         {
  214.             p = (char *) malloc( strlen( fname ) + 2L );
  215.             *p = '/';
  216.             p++;
  217.             strcpy( p, fname );
  218.             p--;
  219.         }
  220.         free( fname );
  221.         return p;
  222.     }
  223.     else
  224.         return fname;
  225. }
  226.  
  227. /* Find out if a Mac file is text (i.e., should convert newlines) */
  228.  
  229. Boolean istextfile( char * name )
  230. {
  231.     OSErr err;
  232.     char tmp[255];
  233.     FInfo finderStuff;
  234.     
  235.     strcpy( tmp, name );
  236.     c2pstr( tmp );
  237.     err = GetFInfo( tmp, 0, &finderStuff );
  238.     check( err, "Getting finder info for local file" );
  239.     return( finderStuff.fdType == 'TEXT' );
  240. }
  241.  
  242. /* Set Macintosh file types */
  243.  
  244. static void set_filetype( char * name, OSType type, OSType creator )
  245. {
  246.  
  247.     OSErr err;
  248.     char tmp[255];
  249.     FInfo finderStuff;
  250.     
  251.     strcpy( tmp, name );
  252.     c2pstr( tmp );
  253.     err = GetFInfo( tmp, 0, &finderStuff );
  254.     check( err, "Getting finder info for local file" );
  255.     finderStuff.fdType = type;
  256.     finderStuff.fdCreator = creator;
  257.     err = SetFInfo( tmp, 0, &finderStuff );
  258.     check( err, "Setting file type" );
  259. }
  260.  
  261. /* Parse arguments, setting flags, etc */
  262.  
  263. static void parse_args( int argc, char ** argv )
  264. {
  265.     int i;
  266.     
  267.     if (argc < 2)
  268.         bomb( "Must specify source & destination files", NULL );
  269.         
  270.     for (i = 1; i < argc; i++)
  271.     {
  272.         if (argv[i][0] == '-')            /* Option flag */
  273.             switch( argv[i][1] ) 
  274.             {
  275.             case 'p':
  276.                 progress_flag = true;
  277.                 break;
  278.             
  279.             case 'd':
  280.                 debug_flag = true;
  281.                 break;
  282.                 
  283.             case 'y':
  284.                 stomp_dest_flag = true;
  285.                 break;
  286.                 
  287.             case 'n':
  288.                 user_nl_flag = true;
  289.                 cvt_nl_flag = false;
  290.                 break;
  291.                 
  292.             case 'u':
  293.                 if (i + 1 == argc)
  294.                     bomb( "Must supply unix user name for -u", NULL );
  295.                 i++;
  296.                 unix_name = strdup( argv[i] );
  297.                 break;
  298.                 
  299.             default:
  300.                 bomb( "Unknown flag: ", argv[i] );
  301.                 break;
  302.             }
  303.         else                            /* Filename */
  304.         {
  305.             char * p;
  306.             char * filename;
  307.             char * host;
  308.             
  309.             if (source_file && dest_file)
  310.                 bomb( "Extra argument: ", argv[i] );
  311.             
  312.             p = argv[i];
  313.             while ((*p) && (*p != '@') && (*p != '!'))    /* Hunt for hostname mark */
  314.                 p++;
  315.             if (*p)                        /* Found host */
  316.             {
  317.                 *p = '\0';
  318.                 
  319.                 host = strdup( argv[i] );
  320.                 p++;
  321.                 filename = strdup( p );
  322.                 filename = unixify( filename );
  323.             }
  324.             else
  325.             {
  326.                 filename = strdup( argv[i] );
  327.                 host = NULL;
  328.             }
  329.             
  330.             if (source_file)
  331.             {
  332.                 dest_file = filename;
  333.                 dest_host = host;
  334.             }
  335.             else
  336.             {
  337.                 source_file = filename;
  338.                 source_host = host;
  339.             }
  340.         }                                /* else */
  341.     }                                    /* for */
  342.  
  343.     if (source_host && dest_host)
  344.         bomb( "Source or destination must be a local filename", NULL );
  345.         
  346.     if (! (source_host || dest_host))
  347.         bomb( "Use the duplicate command to copy local files", NULL );
  348.     
  349.     if (! unix_name)
  350.         unix_name = getenv( "UnixName" );
  351.         
  352.     if (! unix_name)
  353.         bomb( "Must set {UnixName} variable or use -u 'user'", NULL );
  354.  
  355.     /* See if just the dir name was given, if so, must fill in filename */
  356.     if (dest_file[strlen( dest_file ) - 1] == (dest_host ? '/' : ':'))
  357.     {
  358.         char * p;
  359.         p = getleaf( source_file );
  360.         sprintf( buffer, "%s%s", dest_file, p );
  361.         free( dest_file );
  362.         dest_file = strdup( buffer );
  363.     }
  364. }
  365.  
  366. /*
  367.  * Networking stuff
  368.  */
  369.  
  370. void open_remote()
  371. {
  372.     OSErr err;
  373.     char full_hostname[255];
  374.     unsigned long ip_address, local_address;
  375.     /* long save_dirid; */
  376.     short save_vRefNum;
  377.     int open_attempts = 5;
  378.     unsigned short local_port = 514;    /* Must be > 512 to be a BSD "priviledged port" */
  379.     
  380.     /* Totally disgusting - the DNR leaves the working directory in the sytem folder! */
  381.     err = GetVol( NULL, &save_vRefNum );
  382.     check( err, "caching working directory" );
  383.  
  384.     err = InitNetwork();
  385.     check( err, "Opening network" );
  386.     
  387.     strcpy( full_hostname, source_host ? source_host : dest_host );
  388.     
  389.     err = ConvertStringToAddr( full_hostname, &ip_address );
  390.     check( err, "Looking up hostname" );
  391.     
  392.     if (debug_flag)
  393.         fprintf( stderr, "# Remote host %s is %d.%d.%d.%d\n", 
  394.                  full_hostname,
  395.                  (ip_address >> 24) & 0xFF, (ip_address >> 16) & 0xFF,
  396.                  (ip_address >> 8) & 0xFF, ip_address & 0xFF );
  397.     
  398.     err = CreateStream( &tcp_stream, BUFSIZE );
  399.     check( err, "Creating TCP I/O stream" );
  400.     
  401.     /* Try this multiple times - sometimes the port needs to settle for
  402.      * consequitive opens */
  403.     do
  404.     {
  405.         err = LowTCPOpenConnection( tcp_stream, TIMEOUT, ip_address, RCMD_PORT,
  406.                                     &local_address, &local_port);
  407.         if (err == openFailed)
  408.         {
  409.             long then, delay_ticks = 10;
  410.             
  411.             then = TickCount();
  412.             while (TickCount() < then + delay_ticks)
  413.                 SystemTask();
  414.         }
  415.     }
  416.     while ((err == openFailed) && (open_attempts-- != 0));
  417.     check( err, "Opening connection" );
  418.     
  419.     if (debug_flag)
  420.         fprintf( stderr, "# local addr is: %d.%d.%d.%d, port %d\n",
  421.                  (local_address >> 24) & 0xFF, (local_address >> 16) & 0xFF,
  422.                  (local_address >> 8) & 0xFF, local_address & 0xFF,
  423.                  local_port );
  424.     if (debug_flag)
  425.         fprintf( stderr, "# tcp_stream = %08x\n", tcp_stream );
  426.     
  427.     err = SetVol( NULL, save_vRefNum );
  428.     check( err, "restoring working directory" );
  429. }
  430.  
  431. /*
  432.  * Check the response the server gave to the last request.  0=OK, others
  433.  * indicate problems, followed by a message
  434.  */
  435. static void check_response()
  436. {
  437.     char * p = buffer;
  438.     unsigned short length = BUFSIZE;
  439.     OSErr err;
  440.     
  441.     err = RecvData( tcp_stream, buffer, &length, false );
  442.     check( err, "Remote doesn't acknowledge");
  443.  
  444.     switch (buffer[0])  {
  445.         case 0:
  446.             return;                        /* Worked fine. */
  447.  
  448.         default:                        /* Remote err (terminate string first) */
  449.             while (((p-buffer) < BUFSIZE) && (*p != NEWLINE) && (*p)) p++; 
  450.             p--;
  451.               *p = '\0';
  452.             p = buffer;
  453.             p++;
  454.             bomb( "Remote error: ", p );
  455.     }
  456. }
  457.  
  458. static void go_ahead()
  459. {
  460.     OSErr err;
  461.     
  462.     buffer[0] = '\0';
  463.     err = SendData( tcp_stream, buffer, (unsigned short) 1, false );
  464.     check( err, "Sending ack to remote host" );
  465. }
  466.  
  467. /*
  468.  * With the connection open, go through the rcmd user verification.  The
  469.  * protocol used is described in RSHD(8C) of the BSD manual.
  470.  */
  471. static void init_rcmd()
  472. {
  473.     char * p;
  474.     unsigned short buflen;
  475.     OSErr err;
  476.     int i;
  477.  
  478.     /* Layout of rcmd message is:
  479.      * 0NsssssssNnnnnnnnNcccccccN
  480.      * where '0' is ascii '0' followed by null (unused 2nd stream port)
  481.      *       'N' is the null character ('\0')
  482.      *       'sssss...' is the identity to use at the server.
  483.      *       'nnnnn...' is the identity to use at the client.
  484.      *       'cccc....' is the command to be passed to the remote shell.
  485.      */
  486.  
  487.     buffer[0] = '0';                /* 2nd stream port (none) */
  488.     buffer[1] = '\0';
  489.  
  490.     p = &buffer[2];
  491.     strcpy( p, unix_name );
  492.     p = &p[strlen(p) + 1];            /* Move past remote user name */
  493.     strcpy( p, unix_name );
  494.     p = &p[strlen(p) + 1];            /* Move past local user name */
  495.     
  496.     if (dest_host)
  497.         sprintf( p, "rcp -t %s", dest_file );
  498.     else
  499.         sprintf( p, "rcp -f %s", source_file );
  500.         
  501.     p = &p[strlen(p) + 1];            /* Move past rcp command */
  502.     buflen = p - buffer;
  503.     
  504.     if (debug_flag)
  505.     {
  506.         fprintf( stderr, "# Sending RCMD string: '" );
  507.         for (i = 0; i < buflen; i++)
  508.             fprintf( stderr, "%c", buffer[i] == '\0' ? '_' : buffer[i] );
  509.         fprintf( stderr, "'\n" );
  510.     }
  511.     err = SendData( tcp_stream, buffer, buflen, false );
  512.     check( err, "Starting copy" );
  513.     check_response();
  514. }
  515.  
  516. /*
  517.  * File transfer
  518.  */
  519.  
  520. /* Send file from Mac to Unix host */
  521.  
  522. static void send_file()
  523. {
  524.     FILE * f;
  525.     char * p;
  526.     long file_len, return_len;
  527.     OSErr err;
  528.     int i;
  529.     
  530.     if (! (f = fopen( source_file, "r" )))
  531.     {
  532.         perror( "# Can't open" );
  533.         bomb( "Copy failed sending file", NULL );
  534.         exit( -1 );
  535.     }
  536.     
  537.     if (! user_nl_flag)                    /* Only reset nl conversion if unspecified by user */
  538.         if (! istextfile( source_file ))
  539.             cvt_nl_flag = false;
  540.     
  541.     /* In accordance with the RCP protocol, first
  542.      *      C0xxx sss rrr\n
  543.      * is sent with "xxx" as the files protection, "sss" as its decimal size,
  544.      * and 'rrr' as the root part of its name.  There are other possible
  545.      * control records; 'T' for time, and 'D' and 'E' for directories, but
  546.      * these are not implemented.
  547.      */
  548.  
  549.     p = getleaf( dest_file );            /* Source file? */
  550.         
  551.     fseek( f, (long) 0, 2 );            /* Get file length */
  552.     file_len = ftell( f );
  553.     rewind( f );
  554.     
  555.     sprintf( buffer, "C0644 %d %s\n", file_len, p );
  556.     buffer[strlen(buffer)-1] = NEWLINE;    /* Switch to unix newline */
  557.     
  558.     err = SendData( tcp_stream, buffer, (unsigned short)strlen(buffer), false );
  559.     check( err, "Sending control string" );
  560.  
  561.     check_response();
  562.     
  563.     for (i = 0; i < file_len; i += return_len )
  564.     {
  565.         return_len = fread( buffer, 1, sizeof( buffer ), f );
  566.         if (return_len)
  567.         {
  568.             convert_nl( buffer, return_len );
  569.             err = SendData( tcp_stream, buffer, (unsigned short) return_len, false );
  570.             check( err, "Sending data" );
  571.         }
  572.     }
  573.     fclose( f );
  574.     
  575.     go_ahead();
  576. }
  577.  
  578. /* Get file from Unix host to the mac */
  579.  
  580. static void recv_file()
  581. {
  582.     FILE * f;
  583.     unsigned short length;
  584.     long i, remote_length;
  585.     int file_mode;
  586.     char remote_name[255];
  587.     OSErr err;
  588.     
  589.     if (! stomp_dest_flag)
  590.     {
  591.         if (f = fopen( dest_file, "r" ))
  592.         {
  593.             fclose( f );
  594.             bomb( "File exists (use -y to overwrite): ", dest_file );
  595.         }
  596.     }
  597.     
  598.     if (!(f = fopen( dest_file, "w" )))
  599.     {
  600.         perror( "Can't open for write" );
  601.         bomb( "Copy failed", NULL );
  602.         exit( -1 );
  603.     }
  604.     
  605.     go_ahead();
  606.     
  607.     length = BUFSIZE;
  608.     
  609.     err = RecvData( tcp_stream, buffer, &length, false );
  610.     check( err, "Getting control record" );
  611.  
  612.     /*
  613.      * The control record tells us the protection, the (vax) filename, and
  614.      * size (in that order, see send_file()).  All we''re interested in is
  615.      * the size.  Also, the only type we can handle is 'C' (not 'D' or 'T').
  616.      */
  617.  
  618.     if (buffer[0] < ' ')                /* Ctl char means error */
  619.         bomb( "Remote error:", buffer+1 );
  620.  
  621.     if (buffer[0] != 'C')
  622.         bomb( "Unknown or unsupported control record received", NULL );
  623.  
  624.     if ( sscanf( buffer, "C %04o %d %s\n", &file_mode, &remote_length,
  625.                  &remote_name ) != 3 )
  626.         bomb( "Bad control record received", NULL );
  627.  
  628.     go_ahead();
  629.  
  630.     for ( i = 0; i < remote_length; i += length ) {
  631.  
  632.         length = BUFSIZE;
  633.         err = RecvData( tcp_stream, buffer, &length, false );
  634.         convert_nl( buffer, (int) length );
  635.         check( err, "Getting data" );
  636.  
  637.             /* Avoid swallowing the server's go-ahead */
  638.         if ((i + length) > remote_length)
  639.             length--;
  640.  
  641.         fwrite( buffer, 1, (int) length, f );
  642.     }
  643.     
  644.     fclose( f );
  645.  
  646.     if (! user_nl_flag)
  647.         set_filetype( dest_file, 'TEXT', 'MPS ' );
  648.     else
  649.         set_filetype( dest_file, '????', 'MPS ' );
  650.     
  651. /** check_response ();   This hangs because "go-ahead" is eaten above...
  652.                          I trust it for now.
  653. **/
  654.     go_ahead();
  655. }
  656.  
  657. void
  658. main( int argc, char ** argv )
  659. {
  660.     OSErr err;
  661.     
  662.     parse_args( argc, argv );
  663.     if (debug_flag)
  664.         fprintf( stderr, "# source: [%s] %s\n# dest  : [%s] %s\n# Remote User: %s\n", 
  665.                  source_host ? source_host : "LOCAL", source_file ,
  666.                  dest_host ? dest_host : "LOCAL", dest_file, unix_name );
  667.  
  668.     open_remote();
  669.     init_rcmd();
  670.     
  671.     if (dest_host)
  672.         send_file();
  673.     else
  674.         recv_file();
  675.  
  676.     err = CloseConnection( tcp_stream );
  677.     check( err, "Closing TCP connection" );
  678.     ReleaseStream( tcp_stream );
  679.  
  680.     if (progress_flag)
  681.     {
  682.         if (source_host)
  683.             fprintf( stderr, "# Copied %s@%s to %s\n", source_host, source_file, dest_file );
  684.         else
  685.             fprintf( stderr, "# Copied %s to %s@%s\n", source_file, dest_host, dest_file );
  686.     }
  687.     exit( 0 );
  688. }
  689.