Followup & Fix to my first posting:

If you want to reproduce the bug even on a fast connection simply enter in 
File FtpCli

procedure TCustomFtpCli.Next1GetAsync;
        FNext := nil;
->        Sleep(500);

This will cause the data-connection to be established before the client can 
"revoke" it since it receives the 501 response from the FtpSrv.
Once the data-connection is established also on the FtpSrv-side tearing it 
down will cause the "226 File sent ok" to be issued, no matter if an "501 
Invalid RETR" has already been sent.

My investigations show that this behaviour _on server side_ seems to be 
faulty - other FTP Servers always only answer ONCE (with e.g. 501) on the 
control connection if the transfer fails before any data is transferred (on 
the data-channel).

Following a fix:

The data-socket must effectively be stopped from "accepting" a connection 
once the primary transfer command has failed. If the data-socket is simply 
closed then on client-side an error is raised - so my solution is not to 
call datasocket.accept on the incoming connection - by clearing 

procedure TFtpServer.CommandRETR(...);
var ...
{ --- FLD --- begin }
{ --- FLD --- end }

    .... (here comes all the code that is in the procedure right now)

{ --- FLD --- begin }
    if Client.PassiveMode and
       (StrLComp(PChar(Answer), '15', 2)<> 0) then // check for success 
      Client.DataSocket.OnSessionAvailable:= nil;
{ --- FLD --- end }

This must be done for all commands that use the (passive) data connection; 
CommandRETR, CommandSTOR and so on...

best regards,

ps: please forget the last paragraph in my last posting - the described 
situation can't happen

Peter Feldbaumer
p dot feldbaumer at utanet dot at

> I found a bug in the FtpCli - FtpSrv combination.
> Steps to reproduce:
> 1) (Client) put FtpCli into passive mode
> 2) (Server) assign FtpSrv.FtpServerValidateGet
> 3) (Client) request something from the Server - e.g. with FtpClient.Get
> 4) (server) fail the resulting RETR command on the server-side in
> FtpServerValidateGet (either by setting Allowed to false or by raising an
> exception)
> If the connection between Client and Server is slow enough(!) this will 
> lead
> to wrong state in the client state-machine, so that subsequent RETR will
> fail! Below you can find a log that demonstrates the problem (logged on
> client side) - (please note that there are some custom-extended commands -
> namely STATUS and ACCESS - they all expect a one-line answer, just as
> regular ftp-commands).
> The error occurs in line L6. L1 to L4 are as expected, L4 is the result 
> from
> the failed FtpServerValidateGet.
> Then the client issues the next command L5 and it expects a 1 line answer
> (which comes in L7), however L6 is received inbetween as the answer 
> because
> the FtpSrv "unexpectedly" sends "226 File sent ok", although it never has
> transmitted anything. This unexpected answer shifts all subsequent
> server-answer and leads to an unexpected state at some time.
> L1: > PASV
> L2: < 227 Entering Passive Mode (81,189,215,215,78,33).
> L3: > RETR _SYSTEMARCHIVE_5889c6e9f9fb40d68fa23d6d74011566
> L4: < 501 Cannot RETR. IDENTICAL
> L5: > STATUS
> L6: < 226 File sent ok
> L8: < 200 Haupt-Bildschirm: Geteilter Schirm
> L9: > PASV
> L10: < 200 Ok. Access Type set!
> L11: > RETR _SYSTEMSTAT_0_500
> L12: < 227 Entering Passive Mode (81,189,215,215,78,33).
> L13: ErrorMessage: 227 Entering Passive Mode (81,189,215,215,78,33).
> If you work on a fast network connection this problem doesn't occur, 
> because
> L4 AND L6 are received "in one block" and effectively form L4 and L5, 
> which
> doesn't shift anything.
> Reason for the problem:
> In passive mode FtpCli starts a passive data-connection to the server at 
> the
> same time as it sends the RETR command (in TCustomFtpCli.DoGetAsync()), so
> it does NOT wait for a response from the FtpSrv if the RETR-command
> alltogether is valid (in active mode this doesn't happen because then the
> FtpSrv starts the data-connection - and this only happens if the
> FtpSrv.FtpServerValidateGet succeeds).
> As soon as the client receives the "501 Cannot RETR." (triggered by
> TFtpServer.CommandRETR() and the call to FtpSrv.FtpServerValidateGet) it
> closes the data-connection again.
> But this triggers TFtpServer.ClientRetrSessionClosed() which in turn sends
> the"226 File sent ok".
> So the reason for the problem is clear, however I don't know how to 
> correct
> it?
> Should
> a) the passive data-connection only be connected if / as soon as the
> "primary command" is approved by the server
> b) the server simply ignore the ClientRetrSessionClosed and not send
> anything if nothing was transmitted (scheduled to transmit)?
> btw: This led my to discover another fundamental problem with passive mode
> (which luckily obviously doesn't occur in reality):
> On server side (in passive mode) DataSocket.OnSessionConnected,
> DataSocket.OnSessionClosed, DataSocket.OnDataAvailable and
> DataSocket.OnDataSent are assigned in
> TFtpServer.ClientPassiveSessionAvailable(), depending on the scheduled
> command (e.g. RETR).
> Since in FtpCli both, passive-mode data-connection _and_ the desired 
> command
> (e.g. "RETR"), are issued at the same time (although on different ports), 
> it
> _could_ happen that the data-connection is established _before_ the
> control-connection has transmitted the command! This would lead to
> TFtpServer.ClientPassiveSessionAvailable() being called before the server
> knows for which command the passive-data-connection is connected - in 
> return
> effectively failing the rest of the transfer...

To unsubscribe or change your settings for TWSocket mailing list
please goto
Visit our website at

Reply via email to