1

When trying to upload a file to an ftp server running pure-ftpd (configured for FTPS not SFTP, requiring explicit FTP over TLS) via .NET an exception is thrown:

Unhandled exception. System.Net.WebException: The underlying connection was closed: The server committed a protocol violation.

Server logs appear to indicate everything going well up until the client stops communicating after a 150 Accepted data connection log message.

Client logs indicate that bytes were written to the stream but closing the stream seems to trigger the exception.

A file is created on the ftp server; however, no content is written to it.

Other clients (i.e. Filezilla) work to upload files (using the same credentials, from the same network/machine to the same server) without issue.

Other similar problems I've seen suggest changing the various properties of the request (EnableSsl, UseBinary, UsePassive, KeepAlive, Timeout). I've tried various combinations of these properties, but to no avail.

I'm at a loss as to what the problem is . I'm inclined to believe it's a client-side misconfiguration as other languages/clients connect to the server without issue. I don't call myself a C#/.NET developer, so there may be something obvious that I'm missing - any help is appreciated.

using System;
using System.IO;
using System.Net;
using System.Text;

Uri target = new Uri("ftp://ftp.example.com:21/filenamehere");
FtpWebRequest request = (FtpWebRequest)WebRequest.Create(target);
request.Method = WebRequestMethods.Ftp.UploadFile;
request.EnableSsl = true;
request.Credentials = new NetworkCredential("myusername", "password");
request.UseBinary = true;
request.UsePassive = true;
request.KeepAlive = true;
request.Timeout = -1;

Stream fileStream = File.OpenRead(@"testfile.txt");
Stream ftpStream = request.GetRequestStream();

byte[] buffer = new byte[10240];
int read;
while ((read = fileStream.Read(buffer, 0, buffer.Length)) > 0)
{
    ftpStream.Write(buffer, 0, read);
    Console.WriteLine("Uploaded {0} bytes", fileStream.Position);
}

ftpStream.Close();
Console.WriteLine("done...");

client output:

Uploaded 10240 bytes
Uploaded 20480 bytes
Uploaded 30720 bytes
Uploaded 40960 bytes
Uploaded 51200 bytes
Uploaded 61440 bytes
Uploaded 71680 bytes
Uploaded 81920 bytes
Uploaded 92160 bytes
Uploaded 92295 bytes

Unhandled exception. System.Net.WebException: The underlying connection was closed: The server committed a protocol violation at System.Net.FtpWebRequest.SyncRequestCallback(Object obj) at System.Net.CommandStream.InvokeRequestCallback(Object obj) at System.Net.CommandStream.Abort(Exception e) at System.Net.CommandStream.CheckContinuePipeline() at System.Net.FtpWebRequest.DataStreamClosed(CloseExState closeState) at System.Net.FtpDataStream.System.Net.ICloseEx.CloseEx(CloseExState closeState) at System.Net.FtpDataStream.Dispose(Boolean disposing) at System.IO.Stream.Close() at Program.$(String[] args) in /Users/user/MyApp/Program.cs:line 27

server output:

Jan  6 23:46:06 machine1 ftp.debug pure-ftpd: (?@XX.XX.XX.XX) [DEBUG] Command [user] [myusername]
Jan  6 23:46:06 machine1 ftp.debug pure-ftpd: (?@XX.XX.XX.XX) [DEBUG] 331 User myusername OK. Password required
Jan  6 23:46:06 machine1 ftp.info pure-ftpd: (?@XX.XX.XX.XX) [INFO] myusername is now logged in
Jan  6 23:46:06 machine1 ftp.debug pure-ftpd: (myusername@XX.XX.XX.XX) [DEBUG] 230 OK. Current restricted directory is /
Jan  6 23:46:06 machine1 ftp.debug pure-ftpd: (myusername@XX.XX.XX.XX) [DEBUG] Command [pbsz] [0]
Jan  6 23:46:06 machine1 ftp.debug pure-ftpd: (myusername@XX.XX.XX.XX) [DEBUG] 200 PBSZ=0
Jan  6 23:46:06 machine1 ftp.debug pure-ftpd: (myusername@XX.XX.XX.XX) [DEBUG] Command [prot] [P]
Jan  6 23:46:06 machine1 ftp.debug pure-ftpd: (myusername@XX.XX.XX.XX) [DEBUG] 200 Data protection level set to "private"
Jan  6 23:46:06 machine1 ftp.debug pure-ftpd: (myusername@XX.XX.XX.XX) [DEBUG] Command [opts] [utf8 on]
Jan  6 23:46:06 machine1 ftp.debug pure-ftpd: (myusername@XX.XX.XX.XX) [DEBUG] 500 Unknown command
Jan  6 23:46:06 machine1 ftp.debug pure-ftpd: (myusername@XX.XX.XX.XX) [DEBUG] Command [pwd] []
Jan  6 23:46:06 machine1 ftp.debug pure-ftpd: (myusername@XX.XX.XX.XX) [DEBUG] 257 "/" is your current location
Jan  6 23:46:06 machine1 ftp.debug pure-ftpd: (myusername@XX.XX.XX.XX) [DEBUG] Command [type] [I]
Jan  6 23:46:06 machine1 ftp.debug pure-ftpd: (myusername@XX.XX.XX.XX) [DEBUG] 200 TYPE is now 8-bit binary
Jan  6 23:46:06 machine1 ftp.debug pure-ftpd: (myusername@XX.XX.XX.XX) [DEBUG] Command [pasv] []
Jan  6 23:46:06 machine1 ftp.debug pure-ftpd: (myusername@XX.XX.XX.XX) [DEBUG] 227 Entering Passive Mode (YY,YY,YY,YY,YY,YY)
Jan  6 23:46:07 machine1 ftp.debug pure-ftpd: (myusername@XX.XX.XX.XX) [DEBUG] Command [stor] [filenamehere]
Jan  6 23:46:07 machine1 ftp.debug pure-ftpd: (myusername@XX.XX.XX.XX) [DEBUG] 150 Accepted data connection
Colby Clark
  • 181
  • 1
  • 8

1 Answers1

0

While I can't pinpoint with absolute certainty the cause of the issue prompting this question, it appears a .NET update on/around 2019-10 is the culprit. Updating pureftpd to a newer version (at least 1.0.49-r2) appears to make pureftpd play nicer with the updated version of .NET.

Thanks to @Martin Prikryl for the linked question (specifically the references in the answer).

Colby Clark
  • 181
  • 1
  • 8