Search code examples
delphiftpdelphi-7indy

Indy IdFTP fails on Active connection


I'm trying to use Indy's IdFTP to send and receive some files via FTP.

function TDatosFTP.TransfiereFTP(Fm: TForm): boolean;
var
  TimeoutFTP: integer;
begin
  Result := false;
  with TIdFTP.Create(Fm) do
  try
    try
      TimeoutFTP := 2000;
      Host := Servidor;
      Port := 21;
      UserName := Usuario;
      PassWord := Contra;
      Passive := Pasivo;
      Connect(true, TimeoutFTP);
      if not Connected then
      begin
        Error := true;
      end
      else
      begin
        TransferType := ftASCII;
        if Binario then
          TransferType := ftBinary;

        OnWorkEnd := FinDeTransmision;
        if Descargar then
          Get(Remoto , Local, True)
        else
          Put(InterpretarRutaEspecial(Local), Remoto, True);

        if Descargar and Borrar then
          Delete(Remoto);
        Disconnect;

        Result := true;
        Fm.Hide;
      end;
    Except on E: Exception do
      Mensaje := E.Message;
    end;
  finally
    Free;
  end;
  if not Result then
    ErrorTransmision;
end;

Whenever I try to do a PUT/GET on Active mode I get the following error: EIdProtocolReplyError: 'Failed to establish connection". It works fine on Passive mode.

The thing is that I want to use Indy (used elsewhere in the project) but the previous version of the code, using OverbyteIcsFtpCli works fine both in Active and Passive mode.

This is the code using OverbyteIcsFtpCli:

function TDatosFTP.TransfiereFTP(Fm: TForm): boolean;
begin
  with TFtpClient.Create(Fm) do
  try
    HostName := Servidor;
    Port := '21';
    UserName := Usuario;
    PassWord := Contra;
    HostDirName := '';
    HostFileName := Origen;
    LocalFileName := InterpretarRutaEspecial(Destino);
    Binary := Binario;
    Passive := Pasivo;

    OnRequestDone := FinDeTransmision;
    if Descargar then
      Result := Receive
    else
      Result := Transmit;
    OnRequestDone := nil;

    if Descargar and Borrar then
      Delete;

    Result := Result and not Error;
    Fm.Hide;
    if not Result then
      ErrorTransmision;

  finally
    Free;
  end;
end;

So I took a look under the hood using wireshark and I found that Indy's FTP is not answering some messages from the server.

This is the file-transmission handshake with OverBytes' FTP:

OverByte FTP

I've highlighted in yellow the two packets sent between server and client that start the data transmission. Now let's see what happens with Indy's FTP:

Indy FTP

The server is sending the packet to start the file transmission but IdFTP is not answering.

I've seen this question but this two tests where ran in the same computer, same network connection, same firewall, etc. Also this one, but I want the FTP to work both in active and passive modes.

What's happening?


Solution

  • In an Active mode transfer, an FTP server creates an outgoing TCP connection to the receiver.

    Your Wireshark captures clearly show that the FTP server in question is creating that transfer connection BEFORE sending a response to the RETR command to let your client know that the connection is proceeding. TFtpClient is accepting that connection before receiving the RETR response. But TIdFTP waits for the RETR response before it will then accept the transfer connection (this also applies to TIdFTP's handling of STOR/STOU/APPE commands, too).

    LPortSv.BeginListen; // <-- opens a listening port for transfer
    ...
    SendPort(LPortSv.Binding); // <-- sends the PORT command
    ...
    SendCmd(ACommand, [125, 150, 154]); // <-- sends the RETR command and waits for a response!
    ...
    LPortSv.Listen(ListenTimeout); // <-- accepts the transfer connection
    ...
    

    Re-reading RFC 959, it says the following:

    The passive data transfer process (this may be a user-DTP or a second server-DTP) shall "listen" on the data port prior to sending a transfer request command. The FTP request command determines the direction of the data transfer. The server, upon receiving the transfer request, will initiate the data connection to the port. When the connection is established, the data transfer begins between DTP's, and the server-PI sends a confirming reply to the user-PI.

    ICS is asynchronous, so this situation is not a big deal for it to handle. But Indy uses blocking sockets, so TIdFTP will need to be updated to account for this situation, likely by monitoring both command and transfer ports simultaneously so it can act accordingly regardless of the order in which the transfer connection and the command response arrive in.

    I have opened a ticket in Indy's issue tracker for this:

    #300: TIdFTP fails on Active mode transfer connection with vsFTPd

    UPDATE: the fix has been merged into the main code now.