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;
...
SetErrorMessage;
FNext := nil;
-Sleep(500);
FDataSocket.Close;
DestroyLocalStream;
...
end;
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
datasocket.OnSessionAvailable.
procedure TFtpServer.CommandRETR(...);
var ...
begin
{ --- FLD --- begin }
try
{ --- FLD --- end }
(here comes all the code that is in the procedure right now)
{ --- FLD --- begin }
finally
if Client.PassiveMode and
(StrLComp(PChar(Answer), '15', 2) 0) then // check for success
150..159
begin
Client.DataSocket.OnSessionAvailable:= nil;
end;
end;
{ --- FLD --- end }
end;
This must be done for all commands that use the (passive) data connection;
CommandRETR, CommandSTOR and so on...
best regards,
Peter
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
L7: ACCESS SYSTEM
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
the226 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