FTP Raw Command SendCommand STOR

0 votes
asked Sep 8, 2011 by ktabarez (150 points)
edited Nov 5, 2012

When issuing raw commands against the a FileZilla server to upload a file, I receive either a "550 Filename invalid" or a "425 Can't open data connection.". There is nothing in the rebex log file that helps.

The reason why I'm using raw commands against the server is because I need to connect to a different FTPS server that requires the "PUT" raw command when uploading files. Using the Rebex PutFile method the FTPS server rejects the upload because the "STOR" command is being sent.

Here is the code when connecting to the FileZilla server:

        Ftp ftp = new Ftp();
        ftp.CommandSent += FtpCommandSentEventHandler;
        ftp.LogWriter = new Rebex.FileLogWriter(@"F:\REBEXLog.txt", Rebex.LogLevel.Verbose);

        ftp.Connect("localhost", 990, new TlsParameters { CertificateVerifier = CertificateVerifier.AcceptAll, AllowedSuites = TlsCipherSuite.Secure, Version = TlsVersion.SSL30 | TlsVersion.TLS10, CommonName = "localhost"}, FtpSecurity.Implicit);

        ftp.Passive = true;

        ftp.Login("user", "1234");
        ftp.SendCommand("PBSZ 0");
        ftp.ReadResponse();
        ftp.SendCommand("PROT P");
        ftp.ReadResponse();
        ftp.SendCommand("TYPE I");
        ftp.ReadResponse();
        ftp.SendCommand("PASV");
        ftp.ReadResponse();
        ftp.SendCommand("STOR " +  @"F:\d.zip");//550 Filename invalid
        ftp.ReadResponse();
        ftp.SendCommand("STOR " +  "d.zip");//425 Can't open data connection.
        //Is the client expecting the file to be in a certain directory?  
        //If so, how can I determine the path to the directory when using the raw STOR command?
        ftp.ReadResponse();
Applies to: Rebex FTP/SSL

3 Answers

0 votes
answered Sep 8, 2011 by Lukas Pokorny (92,630 points)
edited Nov 2, 2012
 
Best answer

UPDATE: since release 2012 R3 there is better support for setting a custom upload command.

The SendCommand method is not a command-line-like interface to Rebex FTP API. All it does is sending the supplied commands to the server as-is, without any further processing. This is fine for simple commands, but won't work for commands that need other actions to be done at the client. In case of STOR command, the client has to connect to the IP/port returned by the server in response to the PASV command. The PutFile method does that, but SendCommand alone does not - that's why the log doesn't contain anything useful except the 425 error response.

To make the code work, you would have to handle the data transfer yourself. This is not recommended, but it can be done:

        Ftp ftp = new Ftp();
        ftp.CommandSent += FtpCommandSentEventHandler;

        ftp.LogWriter = new Rebex.FileLogWriter(@"F:\REBEXLog.txt", Rebex.LogLevel.Verbose);

        ftp.Connect("localhost", 990, new TlsParameters { CertificateVerifier = CertificateVerifier.AcceptAll, AllowedSuites = TlsCipherSuite.Secure, Version = TlsVersion.SSL30 | TlsVersion.TLS10, CommonName = "localhost" }, FtpSecurity.Implicit);
        ftp.Login("user", "1234");

        // after each ftp.ReadResponse, the Group property of the
        // resulting FtpResponse object should be checked
        ftp.SendCommand("PBSZ 0");
        ftp.ReadResponse();
        ftp.SendCommand("PROT P");
        ftp.ReadResponse();
        ftp.SendCommand("TYPE I");
        ftp.ReadResponse();
        ftp.SendCommand("PASV");
        string rawEP = ftp.ReadResponse().Description;

        // parse the IP address and port from the PASV response
        // (this sample code doesn't check for any unusual or unparsable values)
        int epStart = rawEP.LastIndexOf('(') + 1;
        int epEnd = rawEP.IndexOf(')', epStart);
        rawEP = rawEP.Substring(epStart, epEnd - epStart);
        string[] epPart = rawEP.Split(',');
        string ip = string.Join(".", epPart, 0, 4);
        int port = int.Parse(epPart[4], CultureInfo.InvariantCulture) * 256;
        port += int.Parse(epPart[5], CultureInfo.InvariantCulture);

        // initialize the transfer at the server side
        ftp.SendCommand("STOR " + "d.zip");

        // open data connection
        TlsSocket dataSocket = new TlsSocket();
        dataSocket.Connect(ip, port);

        // upgrade the data connection to SSL
        dataSocket.Negotiate();

        // read and check the STOR command response
        FtpResponse response = ftp.ReadResponse();
        if (response.Group != 1)
            throw new ApplicationException("FTP error " + response.Description);

        // send data in 4K blocks (can be enlarged for better throughput)
        byte[] buffer = new byte[4096];
        using (Stream input = File.OpenRead(@"F:\d.zip"))
        {
            for (;;)
            {
                int count = input.Read(buffer, 0, buffer.Length);
                if (count == 0)
                    break;
                dataSocket.Send(buffer, 0, count, SocketFlags.None);
            }
        }

        // close the data connection (=end of upload)
        dataSocket.Close();

        // read and check the server response
        response = ftp.ReadResponse();
        if (response.Group != 2)
            throw new ApplicationException("FTP error " + response.Description);

By the way, please let us know which FTP server requires a PUT command instead of STOR - we have never seen anything like that. Are you sure it's really the case? PUT command is used by some command-line FTP clients, but they actually send STOR to the server during the process.

commented Sep 8, 2011 by ktabarez (150 points)
edited Oct 19, 2011

This is awesome!

That's what I thought too - command-line FTP clients using PUT but sending STOR to the server.

There was an instance when we had to connect via FTP over IPsec using the windows command line because clients' IT department informed us we needed to issue a PUT command due to the STOR command not being supported by their server. It was strange to see the FTP windows command line being able to successfully transfer files even though it was sending a STOR during the process while the FTP Rebex component wasn't. This client was using an EDTUP Unix system.

In this case, the client is using an IBM z/os Unix system.

A small update to the code above. Before upgrading the connection to SSL, the TlsSocket parameters need to be set.

        // set TlsParameters
        dataSocket.Parameters = ftp.TlsSocket.Parameters;//new TlsParameters { CertificateVerifier = CertificateVerifier.AcceptAll, AllowedSuites = TlsCipherSuite.Secure, Version = TlsVersion.SSL30 | TlsVersion.TLS10, CommonName = "localhost" };

        // upgrade the data connection to SSL
        dataSocket.Negotiate();
commented Sep 9, 2011 by Lukas Pokorny (92,630 points)
edited Sep 9, 2011

Interesting!

Yes, I forgot the Parameters (they were not needed with our test server) and re-using the control connection ones is a good idea. One more tip: if you remember dataSocket.Session after dataSocket.Negotiate, you can use SSL's session-resume capability to speed-up subsequent negotiations a bit (if the server support this) - just assign the previous session to dataSocket.Parameters.Session. When transfering lot of small files, this can make a big difference.

0 votes
answered Jan 9, 2012 by Lukas Matyska (44,190 points)
edited Nov 2, 2012

UPDATE: since release 2012 R3 there is better support for setting a custom upload command.

Here is the .NET VB version of the code above

Dim ftp As New Ftp()

ftp.LogWriter = New Rebex.FileLogWriter("F:\REBEXLog.txt", Rebex.LogLevel.Verbose)

Dim p As New TlsParameters()
p.CertificateVerifier = CertificateVerifier.AcceptAll
p.AllowedSuites = TlsCipherSuite.Secure
p.Version = TlsVersion.SSL30 Or TlsVersion.TLS10
p.CommonName = "localhost"

ftp.Connect("localhost", 990, p, FtpSecurity.Implicit)
ftp.Login("user", "1234")

ftp.ChangeDirectory("/incoming")

' after each ftp.ReadResponse, the Group property of the '
' resulting FtpResponse object should be checked '
ftp.SendCommand("PBSZ 0")
ftp.ReadResponse()
ftp.SendCommand("PROT P")
ftp.ReadResponse()
ftp.SendCommand("TYPE I")
ftp.ReadResponse()
ftp.SendCommand("PASV")
Dim rawEP As String = ftp.ReadResponse().Description

' parse the IP address and port from the PASV response '
' (this sample code doesn''t check for any unusual or unparsable values) '
Dim epStart As Integer = rawEP.LastIndexOf("("C) + 1
Dim epEnd As Integer = rawEP.IndexOf(")"C, epStart)
rawEP = rawEP.Substring(epStart, epEnd - epStart)
Dim epPart As String() = rawEP.Split(","C)
Dim ip As String = String.Join(".", epPart, 0, 4)
Dim port As Integer = Integer.Parse(epPart(4), CultureInfo.InvariantCulture) * 256
port += Integer.Parse(epPart(5), CultureInfo.InvariantCulture)

' initialize the transfer at the server side '
ftp.SendCommand("STOR" & "d.zip")

' open data connection '
Dim dataSocket As New TlsSocket()
dataSocket.Connect(ip, port)

' upgrade the data connection to SSL '
dataSocket.Negotiate()

' read and check the STOR command response '
Dim response As FtpResponse = ftp.ReadResponse()
If response.Group <> 1 Then
    Throw New ApplicationException("FTP error " + response.Description)
End If

' send data in 4K blocks (can be enlarged for better throughput) '
Dim buffer As Byte() = New Byte(4095) {}
Using input As Stream = File.OpenRead("F:\d.zip")
    While True
        Dim count As Integer = input.Read(buffer, 0, buffer.Length)
        If count = 0 Then
            Exit While
        End If
        dataSocket.Send(buffer, 0, count, SocketFlags.None)
    End While
End Using

' close the data connection (=end of upload) '
dataSocket.Close()

' read and check the server response '
response = ftp.ReadResponse()
If response.Group <> 2 Then
    Throw New ApplicationException("FTP error " + response.Description)
End If
0 votes
answered Nov 2, 2012 by Tomas Knopp (58,890 points)
edited Nov 5, 2012

Sending PUT command instead of the default STOR is easy since release 2012 R3.

We have added a property UploadCommand to Ftp object's Settings which can be used to set a custom upload command (e.g. PUT):

        Ftp ftp = new Ftp();

        // specify the command to be used for uploading to the FTP server
        ftp.Settings.UploadCommand = "PUT";

        ftp.Connect("server");
        ftp.Login("user", "password");

        // upload the file into the current remote directory
        ftp.PutFile("file", "file");

        ftp.Disconnect();
...