{ WARNING: Does not really do ASCII mode transfers (CRLF translations) }

{$SYMTAB}
{$DEBUG}
{$SIZE}
{$SPEED}

Program FTP;

{Update History}
{  890317   DRH   Initial. }
{  890802   DAV   Change file open mode to mode text. }
{  910828   DAV   Remove requirement for pascal runtime code. }


     { This program implements a very simple FTP client program.  
       The objective is to provide a useful example of the use of 
       the Application Programming Interface (API) to Internet
       CT.  The code is selfcontained, no references are made to 
       external components (other than procedures and functions 
       from CTOS.Lib).
       
       You may wish to add additional error checking and recovery 
       procedures.
     }
{==================================================================}

Const

   LowerCase_g    = 67;
   UpperCase_G    = 47;

   sControlBuffer = 1024;
   sDataBuffer    = 2048;
   sBsBuffer      = 2048;

   ModeAppend     = 6D61;
   ModeRead       = 6D72;
   ModeModify     = 6D6D;
   ModeText       = 6D74;
   ModeWrite      = 6D77;

   ErcEOF         = 1;

   TcpState_CLOSED         = 0;
   TcpState_LISTEN         = 1;
   TcpState_SYN_SENT       = 2;
   TcpState_SYN_RECEIVED   = 3;
   TcpState_ESTABLISHED    = 4;
   TcpState_FINWAIT1       = 5;
   TcpState_FINWAIT2       = 6;
   TcpState_CLOSE_WAIT     = 7;
   TcpState_CLOSING        = 8;
   TcpState_LAST_ACK       = 9;
   TcpState_TIME_WAIT      = 10;

   sUCB                    = 95;



{==================================================================}

Type

   Quad                       = Integer4;
   ErcType                    = Word;
   pByte                      = Ads of Byte;
   pWord                      = Ads of Word;
   pBoolean                   = Ads of Boolean;

   ParameterType              = Record
      Pb                [000] : pByte;
      Cb                [004] : Word;
   End;                 {006}

   UserControlBlockType       = Record
      LoginID           [000] : Word;
      DefaultVol        [002] : LString(12);
      DefaultDir        [015] : LString(12);
      DefaultPassword   [028] : LString(12);
      DefaultPrefix     [041] : LString(40);
      DefaultNode       [082] : Lstring(12);
   End;                 {095}

   BSWAType                   = Array[0..64] of Word;
   BsBufferType               = Array[1..sBsBuffer] of Byte;

   TcbType                    = Record
      Event          [000]    : Word;
      Param          [002]    : Word;
      LocalIpAddr    [004]    : Quad;
      LocalPort      [008]    : Word;
      RemoteIpAddr   [010]    : Quad;
      RemotePort     [014]    : Word;
      ConnState      [016]    : Byte;
      CloseReason    [017]    : Byte;
      ICMPType       [018]    : Byte;
      ICMPCode       [019]    : Byte;
      Window         [020]    : Word;
      RecvCnt        [022]    : Word;
      SendCnt        [024]    : Word;
   End; {TcbType}    {026}



{==================================================================}

Var [Public]

   ControlHandle              : Word;
   DataHandle                 : Word;

   fPush                      : Boolean;
   fUrgent                    : Boolean;

   ControlBuffer              : Array[1..sControlBuffer] of Byte;
   DataBuffer                 : Array[1..sDataBuffer] of Byte;

   BSWA                       : BSWAType;
   BsBuffer                   : BsBufferType;
   OpenMode                   : Word;

   UCB                        : UserControlBlockType;

   TcpOpenOptions             : Word;

   Tcb                        : TcbType;
   cbTcbRet                   : Word;

   CRLF                       : Array[1..2] of Byte;

   ResponseCode               : Word;
   sBufferRet                 : Word;

   {Command Parameters}

   RemoteHostName             : Lstring(64);
   RemoteFileSpec             : Lstring(78);
   LocalFileSpec              : Lstring(78);
   UserName                   : Lstring(30);
   UserPass                   : Lstring(30);
   UserAcct                   : Lstring(30);
   TypeCmd                    : Lstring(6);

   XferCmd                    : Lstring(90);
   GetCmd                     : Lstring(5);
   PutCmd                     : Lstring(5);
   UserCmd                    : Lstring(35);
   PassCmd                    : Lstring(35);
   AcctCmd                    : Lstring(35);

Var [Extern]

   bsVid                      : BSWAType;



{==================================================================}

Value

   GetCmd               := 'RETR ';
   PutCmd               := 'STOR ';
   UserCmd              := 'user ';
   PassCmd              := 'pass ';
   AcctCmd              := 'acct ';
   UserName             := 'Anonymous';
   UserPass             := ' ';
   UserAcct             := ' ';
   TypeCmd              := 'TYPE I';

   OpenMode             := ModeText;

   CRLF[1] := 0D;
   CRLF[2] := 0A;



{==================================================================}

         { InternetCT Procedural Interface Definitions }
                                 
Function OpenTcpConnection(
   iLocalPort                 : Word;
   pRemoteHost                : AdsMem;
   sRemoteHost                : Word;
   iRemotePort                : Word;
   OpenType                   : Word;
   iTimeOut                   : Word;
   iPrecedence                : Word;
   iSecurity                  : Word;
   pOptions                   : pWord;
   sOptions                   : Word;
   pHandleRet                 : AdsMem) : ErcType; Extern;

Function OpenTcpConnectionNoWait(
   iLocalPort                 : Word;
   pRemoteHost                : AdsMem;
   sRemoteHost                : Word;
   iRemotePort                : Word;
   OpenType                   : Word;
   iTimeOut                   : Word;
   iPrecedence                : Word;
   iSecurity                  : Word;
   pOptions                   : pWord;
   sOptions                   : Word;
   pHandleRet                 : pWord) : ErcType; Extern;
          
Function GetTCB(
   Handle                     : Word;
   pbTcbRet                   : AdsMem;
   sTcbMax                    : Word;
   pbsTcbRet                  : pWord) : ErcType; Extern;

Function SendTcpMessage(
   cHandle                    : Word;
   fPush                      : Boolean;
   fUrgent                    : Boolean;
   pMessage                   : AdsMem;
   sMessage                   : Word): ErcType; Extern;

Function ReceiveTcpMessage(
   Handle                     : Word;
   pfPush                     : pBoolean;
   pfUrgent                   : pBoolean;
   pMessageRet                : AdsMem;
   sMessageRetMax             : Word;
   pcbMessageRet              : pWord) : ErcType; Extern;

Function CloseTcpConnection(
   Handle                     : Word) : ErcType; Extern;



{==================================================================}

                    { CTOS Library Functions }
                                 
Procedure Beep; Extern;

Procedure CheckErc(
   Erc                        : Word); Extern;

Procedure ErrorExitString(
   Erc                        : Word;
   pbString                   : pByte;
   cbString                   : Word); Extern;

Function OpenByteStream(
   pBSWA                      : AdsMem;
   pbFilespec                 : AdsMem;
   cbFileSpec                 : Word;
   pbPassword                 : AdsMem;
   cbPassword                 : Word;
   mode                       : Word;
   pBuffer                    : AdsMem;
   sBuffer                    : Word)  : ErcType; Extern;

Function WriteBsRecord(
   pBSWA                      : AdsMem;
   pb                         : AdsMem;
   cb                         : Word;
   pcbRet                     : AdsMem): ErcType; Extern;

Function ReadBsRecord(
   pBSWA                      : AdsMem;
   pb                         : AdsMem;
   cb                         : Word;
   pcbRet                     : AdsMem): ErcType; Extern;

Function CloseByteStream(
   pBSWA                      : AdsMem): ErcType; Extern;

Function RgParam(
   iParam                     : Word;
   iSubParam                  : Word;
   pPbCbRet                   : AdsMem) : ErcType; Extern;

Function GetUCB(
   pUCBRet                    : AdsMem;
   sUCBRet                    : Word) : ErcType; Extern;



{==================================================================}

Procedure MoveBytes(
   pbDest                     : pByte;
   pbSource                   : pByte;
   cb                         : Word);

Var
   count                   : Word;

Begin
   If (cb > 0) Then Begin
      For count := 1 to cb Do Begin
         pbDest^ := pbSource^;
         pbSource.r := pbSource.r + 1;
         pbDest.r := pbDest.r + 1;
      End;
   End;
End; {MoveBytes}



{==================================================================}

Procedure LAppend(
   pbDest                  : pByte;
   pbSource                : pByte) [Public];

Var
   DestLen                 : Word;
   SourceLen               : Word;
   pbDestSave              : pByte;
   i                       : Word;

Begin

   pbDestSave := pbDest;
   DestLen := pbDest^;
   SourceLen := pbSource^;
   pbDest.r := pbDest.r + DestLen + 1;
   pbSource.r := pbSource.r + 1;

   For i := 1 To SourceLen Do Begin
      pbDest^ := pbSource^;
      pbDest.r := pbDest.r + 1;
      pbSource.r := pbSource.r + 1;
   End;

   pbDestSave^ := DestLen + SourceLen;
   
End; {LAppend}



{==================================================================}

Procedure LConcat(
   pbDest                  : pByte;
   pbSource                : pByte;
   cbSource				   : Word) [Public];

Var
   DestLen                 : Word;
   i                       : Word;

Begin

   DestLen := pbDest^;
   pbDest^ := DestLen + cbSource;
   pbDest.r := pbDest.r + DestLen + 1;

   For i := 1 To cbSource Do Begin
      pbDest^ := pbSource^;
      pbDest.r := pbDest.r + 1;
      pbSource.r := pbSource.r + 1;
   End;
   
End; {LConcat}



{==================================================================}

Procedure StringFromWord(
   pb                      : pByte;
   cb                      : Word;
   wValue                  : Word);

Var
   count                   : Word;

Begin
   pb.r := pb.r + cb  1;
   For count := 1 to cb
      Do Begin
         pb^ := ( wValue Mod 10) + 30;
         wValue := wValue Div 10;
         pb.r := pb.r 1;
      End;

End; {StringFromWord}



{==================================================================}

Function WordFromString(
   pb                         : pByte;
   cb                         : Word) : Word;

Var
   Power                      : Word;
   wValue                     : Word;
   count                      : Word;

Begin

   wValue := 0;
   If (cb > 0)
      Then Begin
         Power := 1;
         pb.r  := pb.r + cb  1;
         For count := 1 to cb
            Do Begin
               If ( ( pb^ >= 30) And ( pb^ <= 39))
                  Then Begin
                     wValue := wValue +
                              ( Power * Wrd( pb^  30));
                     Power := Power * 10;
                  End
               Else Break;
               pb.r := pb.r 1;
            End;
      End;

   WordFromString := wValue

End; {WordFromString}



{==================================================================}

Procedure PutVidMessage(
   pbMsg                      : AdsMem;
   cbMsg                      : Word;
   ResponseCode               : Word;
   fAbort                     : Boolean) [Public];

Var
   DecimalCode                : Array[1..3] of Byte;
   sMsgRet                    : Word;

Begin

   If fAbort
      Then Begin
         Beep;
         ErrorExitString( ResponseCode, pbMsg, cbMsg);
      End;

   CheckErc( WriteBsRecord( Ads bsVid, pbMsg, cbMsg,
                           Ads sMsgRet));
   If ResponseCode <> 0
      Then Begin
         StringFromWord( Ads DecimalCode, 3, ResponseCode);
         CheckErc( WriteBsRecord( Ads bsVid, Ads DecimalCode, 3,
                                  Ads sMsgRet));
      End;
   CheckErc( WriteBsRecord( Ads bsVid, Ads CRLF, 2,
                            Ads sMsgRet));

End; {PutVidMessage}



{==================================================================}

Procedure SendCommand(
   pbCommand            : AdsMem;
   cbCommand            : Word) [Public];

Var
   cbMessageRet         : Word;

Begin

   CheckErc( SendTcpMessage( ControlHandle,
                           False,  {Push}
                           False,  {Urgent}
                           pbCommand, cbCommand));

   CheckErc( SendTcpMessage( ControlHandle,
                           False,  {Push}
                           False,  {Urgent}
                           Ads CRLF, 2));

   PutVidMessage( pbCommand, cbCommand, 0, False);

End; {SendCommand}



{==================================================================}

Function ReceiveResponse : Word [Public];

Var

   cbMessageRet         : Word;
   Push                 : Boolean;
   Urgent               : Boolean;
   ReceiveErc           : ErcType;

Begin

     { This routine will only work if the FTP Server's response 
       is received in its entirty on each call to ReceiveTcp (a 
       likely event under most circumstances).  If, as is 
       possible on networks which suffer extreme fragmentation, 
       the response is not received on one call to ReceiveTcp 
       then one must look for the carriage return  line feed 
       pair (CRLF, 0x0D0x0A) which termainate clear text lines 
       in Internet communication.
     }
       
   ReceiveErc := ReceiveTcpMessage( ControlHandle,
                              Ads Push, Ads Urgent, 
                              Ads ControlBuffer, sControlBuffer,
                              Ads cbMessageRet);
   If cbMessageRet > 3
      Then ReceiveResponse := WordFromString( Ads ControlBuffer,
                                              3)
      Else ReceiveResponse := 999;

   CheckErc( WriteBsRecord( Ads bsVid,
                           Ads ControlBuffer, cbMessageRet,
                           Ads cbMessageRet));

End; {ReceiveResponse}



{==================================================================}

Procedure LoginToFtp [Public];

Var [Static]
   fLoggedIn            : Boolean;
   fNeedPass            : Boolean;
   fNeedAcct            : Boolean;

Value
   fLoggedIn            := False;
   fNeedPass            := False;
   fNeedAcct            := False;

Begin

     { When the FTP Control connection is established the FTP 
       Server will respond with a welcome message which starts 
       with ResponseCode 220.  Anything else is some sort of 
       problem.
     }
       
   ResponseCode := ReceiveResponse;
   If ResponseCode <> 220 Then
      PutVidMessage( Ads 'Welcome Message ', 16, ResponseCode,
                     True);

   SendCommand( Ads UserCmd[1], UserCmd.Len);
   ResponseCode := ReceiveResponse;
   Case ResponseCode of
      230         : fLoggedIn := True;
      331         : fNeedPass := True;
      332         : fNeedAcct := True;
      Otherwise   PutVidMessage( Ads 'User Command ', 13, 
                                 ResponseCode, True);
   End; {Case}

   If fNeedPass
      Then Begin
         SendCommand( Ads PassCmd[1], PassCmd.Len);
         ResponseCode := ReceiveResponse;
         Case ResponseCode of
            230         : fLoggedIn := True;
            332         : fNeedAcct := True;
            Otherwise   PutVidMessage( Ads 'Pass Command ', 13, 
                                       ResponseCode, True);
         End; {Case}
      End;

   If fNeedAcct
      Then Begin
         SendCommand( Ads AcctCmd[1], AcctCmd.Len);
         ResponseCode := ReceiveResponse;
         If ResponseCode = 230
            Then fLoggedIn := True
            Else PutVidMessage( Ads 'Acct Command ', 13,
                                 ResponseCode, True);
      End;

End; {LoginToFtp}




{==================================================================}

Procedure SendPortCommand(

   OurAddress                 : Quad;
   OurPort                    : Word) [Public];

Var [Public]

   PortCmd                    : Lstring(30);
   wWord                      : Array[1..6] of Word;
   wString                    : String(3);

Begin

     { For the Port command each byte from our IP and local port 
       address are individually expressed as a decimal ASCII 
       number separated by commas.  For example if our IP address 
       is 129.24.5.1 and InternetCT assigned local port 2050 the 
       port command generated would be:
       
       PORT 129,024,005,001,008,002
       
       The first part seems obvious enough because of the way 
       Internet addresses are normally expressed.  Although the 
       IP address value would be 81 18 05 01 in Hexadecimal.  
       When each byte is expressed as it's decimal equivalent we 
       get 129,024,005,001.
       
       The same is true with the port address.  Internally the 
       word value for decimal 2050 is Hex 08 02 hence the 
       008,002.
       
       The leading zeros are optional and may be suppressed.
     }
       
   PortCmd := 'PORT ';

   wWord[1] := WRD( HIBYTE( HIWORD( OurAddress)));
   wWord[2] := WRD( LOBYTE( HIWORD( OurAddress)));
   wWord[3] := WRD( HIBYTE( LOWORD( OurAddress)));
   wWord[4] := WRD( LOBYTE( LOWORD( OurAddress)));
   wWord[5] := WRD( HIBYTE( OurPort));
   wWord[6] := WRD( LOBYTE( OurPort));

   For Var i := 1 To 6 Do Begin
      StringFromWord( Ads wString, 3, wWord[i]);
      LConcat( Ads PortCmd, Ads wString, 3);
      If i < 6
         Then LConcat( Ads PortCmd, Ads ',', 1);
   End;

   SendCommand( Ads PortCmd[1], PortCmd.Len);

End; {SendPortCommand}




{==================================================================}

Procedure PrepareDataConnection [Public];

Begin

     { The FTP data connection is under the control of the FTP 
       Server. That means that the FTP Server will do the active 
       open and initiate the close when it's through sending 
       data.  We must provide the FTP Server with the address and 
       port to use for that active open.  We do that with a PORT 
       command prior to requesting a data transfer.  To do that 
       we must get InternetCT to select a local port for us so 
       we can use it in the PORT command.  That's done with a 
       passivenowait open followed by a GetTCB.  The port is 
       assigned during the open and we learn of it by using the 
       GetTCB command.  Once we know the port assigned by 
       InternetCT we can send the PORT command to the FTP 
       Server.
     }
       
   CheckErc( OpenTcpConnectionNoWait(
                           0,        {Any Local Port}
                           Ads Null, {Remote Host Name}
                           0,        {Remote Host Name Length}
                           0,        {Any Remote Port}
                           0,        {Tcp Open Mode Passive}
                           0,        {TimeOut}
                           0,        {Precedence}
                           0,        { Security}
                           Ads TcpOpenOptions,
                           0,        {Length of 0 = use default}
                           Ads DataHandle));

     { Even though GetTCB is a blocking request the response will 
       be immediate because the first thing that happens after 
       any passive open is a connection state change from CLOSED 
       to LISTEN.
       
       We're only interested in our local address and the local 
       port assigned by InternetCT, so we'll only get the first 
       10 bytes of the TCB.
     }
       
   CheckErc( GetTCB( DataHandle, Ads Tcb, 10, Ads cbTcbRet));

   SendPortCommand( Tcb.LocalIpAddr, Tcb.LocalPort);

   ResponseCode := ReceiveResponse;
   If ResponseCode <> 200 Then
      PutVidMessage( Ads 'Port Command ', 13, ResponseCode,
                     True);

End; {PrepareDataConnection}



{==================================================================}

Procedure Get_File [Public];

Var
   fFinished                  : Boolean;
   ReceiveErc                 : Word;

Begin

   fFinished := False;
   Repeat { Receive Loop }
      ReceiveErc := ReceiveTCPMessage( DataHandle,
                              Ads fPush, Ads fUrgent,
                              Ads DataBuffer, sDataBuffer,
                              Ads sBufferRet);

     { Stream Mode FTP transfers signal EOF by closing the data 
       connection.  The transport service (InternetCT) will not 
       return the ClosingERC until after all the data has been 
       consumed by the application.  However, this technique 
       works in all cases.
     }
       
      If sBufferRet <> 0 Then
         CheckErc( WriteBsRecord( Ads BSWA,
                                 Ads DataBuffer, sBufferRet,
                                 Ads sBufferRet));

      If ReceiveErc <> 0
         Then Begin
            CheckErc( CloseByteStream( Ads BSWA));
            CheckErc( CloseTcpConnection( DataHandle));
            fFinished := True;
         End;

   Until fFinished; { Receive Loop }

End;{Get_File}



{==================================================================}

Procedure Put_File [Public];

Var
   ReadErc                    : Word;
   SendErc                    : Word;
   fFinished                  : Boolean;

Begin

   fFinished := False;
   Repeat { Send Loop }

      ReadErc := ReadBsRecord( Ads BSWA,
                              Ads DataBuffer, sDataBuffer,
                              Ads sBufferRet);

      If sBufferRet <> 0
         Then Begin
            SendErc := SendTCPMessage( DataHandle,
                                       False,   {Push}
                                       False,   {Urgent}
                                       Ads DataBuffer,
                                       sBufferRet);

            If SendErc <> 0
                  Then PutVidMessage( Ads 'Send Error ', 11,
                                       SendErc, True);

            If ReadErc <> 0
               Then Begin
                  fFinished := True;
                  If ReadErc <> ErcEOF
                     Then PutVidMessage( Ads 'Read Error ', 11,
                                          ReadErc, True);
               End;
         End
         Else fFinished := True;

   Until fFinished; { Send Loop }

     { Stream Mode FTP transfers signal EOF by closing the data 
       connection.  
     }
       
   CheckErc( CloseByteStream( Ads BSWA));
   CheckErc( CloseTcpConnection( DataHandle));

End; {Put_File}



{==================================================================}

Procedure Initialize [Public];

Var
   Param                      : ParameterType;

Begin

   CheckErc( GetUCB( Ads UCB, sUCB));

   CheckErc( RgParam( 1, 0, Ads Param));     {Remote Host Name}
   MoveBytes( Ads RemoteHostName[1], Param.pb, Param.cb);
   RemoteHostName.Len := Param.cb;

   CheckErc( RgParam( 2, 0, Ads Param));     {Remote File Spec}
   MoveBytes( Ads RemoteFileSpec[1], Param.pb, Param.cb);
   RemoteFileSpec.Len := Param.cb;

   CheckErc( RgParam( 3, 0, Ads Param));     {Local File Spec}
   MoveBytes( Ads LocalFileSpec[1], Param.pb, Param.cb);
   LocalFileSpec.Len := Param.cb;

   If RgParam( 4, 0, Ads Param) = 0          {FTP Command}
      Then If  (Param.pb^ = LowerCase_g)
            OR (Param.pb^ = UpperCase_G)
               Then Begin
                  LAppend( Ads XferCmd, Ads GetCmd);
                  LAppend( Ads XferCmd, Ads RemoteFileSpec);
                  OpenMode := ModeWrite;
                  End
               Else Begin
                  LAppend( Ads XferCmd, Ads PutCmd);
                  LAppend( Ads XferCmd, Ads RemoteFileSpec);
                  End
      Else Begin
         LAppend( Ads XferCmd, Ads PutCmd);
         LAppend( Ads XferCmd, Ads RemoteFileSpec);
         End;

   If RgParam( 5, 0, Ads Param) = 0          {FTP User Name}
      Then Begin
         MoveBytes( Ads UserName[1], Param.pb, Param.cb);
         UserName.Len := Param.cb;
      End;
   LAppend( Ads UserCmd, Ads UserName);
      
   If RgParam( 6, 0, Ads Param) = 0          {FTP User Pass}
      Then Begin
         MoveBytes( Ads UserPass[1], Param.pb, Param.cb);
         UserPass.Len := Param.cb;
      End;
   LAppend( Ads PassCmd, Ads UserPass);

   If RgParam( 7, 0, Ads Param) = 0          {FTP User Acct}
      Then Begin

         MoveBytes( Ads UserAcct[1], Param.pb, Param.cb);
         UserAcct.Len := Param.cb;
      End;
   LAppend( Ads AcctCmd, Ads UserAcct);
      
   If RgParam( 8, 0, Ads Param) = 0          {FTP Type}
      Then MoveBytes( Ads TypeCmd[TypeCmd.Len],
                     Param.pb, 1);

End;



{==================================================================}

Begin

   Initialize;

   CheckErc( OpenTcpConnection(
                           0,       {Any Local Port}
                           Ads RemoteHostName[1],
                           RemoteHostName.Len,
                           21,      {FTP Control Port Number}
                           1,       {TCP Open Mode Active}
                           0,       {TimeOut}
                           0,       {Precedence}
                           0,       {Security}
                           Ads TcpOpenOptions,
                           0,       {Length of 0 = use default}
                           Ads ControlHandle));

   LoginToFTP;

   SendCommand( Ads TypeCmd[1], TypeCmd.Len);
   ResponseCode := ReceiveResponse;
   If ResponseCode <> 200 Then
      PutVidMessage( Ads 'Type Command ', 13, ResponseCode,
                     True);

   PrepareDataConnection;

     { Send the Xfer command and process the response.  For 
       Stream mode transfers the 150 response will indicate 
       command acceptance and the opening of the data channel.  
       Anything else is a problem.
     }
       
   SendCommand( Ads XferCmd[1], XferCmd.Len);

   ResponseCode := ReceiveResponse;
   If ResponseCode <> 150 Then
      PutVidMessage( Ads 'Transfer Command ', 17,
                     ResponseCode, True);

   CheckErc( OpenByteStream( Ads BSWA,
                  Ads LocalFileSpec[1], LocalFileSpec.Len,
                  Ads UCB.DefaultPassword[1],
                  UCB.DefaultPassword.Len,
                  OpenMode, Ads BsBuffer, sBsBuffer));

   Repeat { Wait For Open }
      CheckErc( GetTCB( DataHandle, Ads Tcb, 26, Ads cbTcbRet));
   Until Tcb.ConnState = TcpState_ESTABLISHED;

   If OpenMode = ModeText
      Then Put_File
      Else Get_File;

     { At this point we must determine if the FTP server's 
       response on the FTP Control Connection indicated success 
       or failure.
     }
       
   ResponseCode := ReceiveResponse;
   If ResponseCode = 226
      Then PutVidMessage( Ads 'Transfer OK ', 12, 0, False)
      Else PutVidMessage( Ads 'Transfer Failed ', 16,
                        ResponseCode, True);

End. {FTP}                                 sBufferRet);

            If