using System; using System.IO; using System.Net.Sockets; using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Collections.Generic; using System.Diagnostics; using System.Net; #if CORE || NET45 using System.Threading.Tasks; #endif namespace FluentFTP { /// /// Stream class used for talking. Used by FtpClient, extended by FtpDataStream /// public class FtpSocketStream : Stream, IDisposable { private SslProtocols m_SslProtocols; public FtpSocketStream(SslProtocols defaultSslProtocols) { m_SslProtocols = defaultSslProtocols; } /// /// Used for tacking read/write activity on the socket /// to determine if Poll() should be used to test for /// socket connectivity. The socket in this class will /// not know it has been disconnected if the remote host /// closes the connection first. Using Poll() avoids /// the exception that would be thrown when trying to /// read or write to the disconnected socket. /// private DateTime m_lastActivity = DateTime.Now; private Socket m_socket = null; /// /// The socket used for talking /// protected Socket Socket { get { return m_socket; } private set { m_socket = value; } } int m_socketPollInterval = 15000; /// /// Gets or sets the length of time in milliseconds /// that must pass since the last socket activity /// before calling Poll() on the socket to test for /// connectivity. Setting this interval too low will /// have a negative impact on performance. Setting this /// interval to 0 disables Poll()'ing all together. /// The default value is 15 seconds. /// public int SocketPollInterval { get { return m_socketPollInterval; } set { m_socketPollInterval = value; } } /// /// Gets the number of available bytes on the socket, 0 if the /// socket has not been initialized. This property is used internally /// by FtpClient in an effort to detect disconnections and gracefully /// reconnect the control connection. /// internal int SocketDataAvailable { get { if (m_socket != null) return m_socket.Available; return 0; } } /// /// Gets a value indicating if this socket stream is connected /// public bool IsConnected { get { try { if (m_socket == null) return false; if (!m_socket.Connected) { Close(); return false; } if (!CanRead || !CanWrite) { Close(); return false; } if (m_socketPollInterval > 0 && DateTime.Now.Subtract(m_lastActivity).TotalMilliseconds > m_socketPollInterval) { FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Testing connectivity using Socket.Poll()..."); if (m_socket.Poll(500000, SelectMode.SelectRead) && m_socket.Available == 0) { Close(); return false; } } } catch (SocketException sockex) { Close(); FtpTrace.WriteStatus(FtpTraceLevel.Warn, "FtpSocketStream.IsConnected: Caught and discarded SocketException while testing for connectivity: " + sockex.ToString()); return false; } catch (IOException ioex) { Close(); FtpTrace.WriteStatus(FtpTraceLevel.Warn, "FtpSocketStream.IsConnected: Caught and discarded IOException while testing for connectivity: " + ioex.ToString()); return false; } return true; } } /// /// Gets a value indicating if encryption is being used /// public bool IsEncrypted { get { #if NO_SSL return false; #else return m_sslStream != null; #endif } } NetworkStream m_netStream = null; /// /// The non-encrypted stream /// private NetworkStream NetworkStream { get { return m_netStream; } set { m_netStream = value; } } #if !NO_SSL SslStream m_sslStream = null; /// /// The encrypted stream /// private SslStream SslStream { get { return m_sslStream; } set { m_sslStream = value; } } #endif /// /// Gets the underlying stream, could be a NetworkStream or SslStream /// protected Stream BaseStream { get { #if NO_SSL if (m_netStream != null) return m_netStream; #else if (m_sslStream != null) return m_sslStream; else if (m_netStream != null) return m_netStream; #endif return null; } } /// /// Gets a value indicating if this stream can be read /// public override bool CanRead { get { if (m_netStream != null) return m_netStream.CanRead; return false; } } /// /// Gets a value indicating if this stream if seekable /// public override bool CanSeek { get { return false; } } /// /// Gets a value indicating if this stream can be written to /// public override bool CanWrite { get { if (m_netStream != null) return m_netStream.CanWrite; return false; } } /// /// Gets the length of the stream /// public override long Length { get { return 0; } } /// /// Gets the current position of the stream. Trying to /// set this property throws an InvalidOperationException() /// public override long Position { get { if (BaseStream != null) return BaseStream.Position; return 0; } set { throw new InvalidOperationException(); } } event FtpSocketStreamSslValidation m_sslvalidate = null; /// /// Event is fired when a SSL certificate needs to be validated /// public event FtpSocketStreamSslValidation ValidateCertificate { add { m_sslvalidate += value; } remove { m_sslvalidate -= value; } } int m_readTimeout = Timeout.Infinite; /// /// Gets or sets the amount of time to wait for a read operation to complete. Default /// value is Timeout.Infinite. /// public override int ReadTimeout { get { return m_readTimeout; } set { m_readTimeout = value; } } int m_connectTimeout = 30000; /// /// Gets or sets the length of time milliseconds to wait /// for a connection succeed before giving up. The default /// is 30000 (30 seconds). /// public int ConnectTimeout { get { return m_connectTimeout; } set { m_connectTimeout = value; } } /// /// Gets the local end point of the socket /// public IPEndPoint LocalEndPoint { get { if (m_socket == null) return null; return (IPEndPoint)m_socket.LocalEndPoint; } } /// /// Gets the remote end point of the socket /// public IPEndPoint RemoteEndPoint { get { if (m_socket == null) return null; return (IPEndPoint)m_socket.RemoteEndPoint; } } /// /// Fires the SSL certificate validation event /// /// Certificate being validated /// Certificate chain /// Policy errors if any /// True if it was accepted, false otherwise protected bool OnValidateCertificate(X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { FtpSocketStreamSslValidation evt = m_sslvalidate; if (evt != null) { FtpSslValidationEventArgs e = new FtpSslValidationEventArgs() { Certificate = certificate, Chain = chain, PolicyErrors = errors, Accept = (errors == SslPolicyErrors.None) }; evt(this, e); return e.Accept; } // if the event was not handled then only accept // the certificate if there were no validation errors return (errors == SslPolicyErrors.None); } /// /// Throws an InvalidOperationException /// /// Ignored /// Ignored /// public override long Seek(long offset, SeekOrigin origin) { throw new InvalidOperationException(); } /// /// Throws an InvalidOperationException /// /// Ignored public override void SetLength(long value) { throw new InvalidOperationException(); } /// /// Flushes the stream /// public override void Flush() { if (!IsConnected) throw new InvalidOperationException("The FtpSocketStream object is not connected."); if (BaseStream == null) throw new InvalidOperationException("The base stream of the FtpSocketStream object is null."); BaseStream.Flush(); } #if ASYNC /// /// Flushes the stream asynchronously /// /// The for this task public override async Task FlushAsync(CancellationToken token) { if (!IsConnected) throw new InvalidOperationException("The FtpSocketStream object is not connected."); if (BaseStream == null) throw new InvalidOperationException("The base stream of the FtpSocketStream object is null."); await BaseStream.FlushAsync(token); } #endif /// /// Bypass the stream and read directly off the socket. /// /// The buffer to read into /// The number of bytes read internal int RawSocketRead(byte[] buffer) { int read = 0; if (m_socket != null && m_socket.Connected) { read = m_socket.Receive(buffer, buffer.Length, 0); } return read; } #if NET45 /// /// Bypass the stream and read directly off the socket. /// /// The buffer to read into /// The number of bytes read internal async Task RawSocketReadAsync(byte[] buffer) { int read = 0; if (m_socket != null && m_socket.Connected) { var asyncResult = m_socket.BeginReceive(buffer, 0, buffer.Length, 0, null, null); read = await Task.Factory.FromAsync(asyncResult, m_socket.EndReceive); } return read; } #endif #if ASYNC && !NET45 /// /// Bypass the stream and read directly off the socket. /// /// The buffer to read into /// The number of bytes read internal async Task RawSocketReadAsync(byte[] buffer) { int read = 0; if (m_socket != null && m_socket.Connected) { read = await m_socket.ReceiveAsync(new ArraySegment(buffer), 0); } return read; } #endif /// /// Reads data from the stream /// /// Buffer to read into /// Where in the buffer to start /// Number of bytes to be read /// The amount of bytes read from the stream public override int Read(byte[] buffer, int offset, int count) { #if !CORE IAsyncResult ar = null; #endif if (BaseStream == null) return 0; m_lastActivity = DateTime.Now; #if CORE return BaseStream.ReadAsync(buffer, offset, count).Result; #else ar = BaseStream.BeginRead(buffer, offset, count, null, null); if (!ar.AsyncWaitHandle.WaitOne(m_readTimeout, true)) { Close(); throw new TimeoutException("Timed out trying to read data from the socket stream!"); } return BaseStream.EndRead(ar); #endif } #if ASYNC /// /// Reads data from the stream /// /// Buffer to read into /// Where in the buffer to start /// Number of bytes to be read /// The for this task /// The amount of bytes read from the stream public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken token) { if (BaseStream == null) return 0; m_lastActivity = DateTime.Now; return await BaseStream.ReadAsync(buffer, offset, count, token); } #endif /// /// Reads a line from the socket /// /// The type of encoding used to convert from byte[] to string /// A line from the stream, null if there is nothing to read public string ReadLine(System.Text.Encoding encoding) { List data = new List(); byte[] buf = new byte[1]; string line = null; while (Read(buf, 0, buf.Length) > 0) { data.Add(buf[0]); if ((char)buf[0] == '\n') { line = encoding.GetString(data.ToArray()).Trim('\r', '\n'); break; } } return line; } /// /// Reads all line from the socket /// /// The type of encoding used to convert from byte[] to string /// The size of the buffer /// A list of lines from the stream public IEnumerable ReadAllLines(System.Text.Encoding encoding, int bufferSize) { int charRead; List data = new List(); byte[] buf = new byte[bufferSize]; while ((charRead = Read(buf, 0, buf.Length)) > 0) { var firstByteToReadIdx = 0; var separatorIdx = Array.IndexOf(buf, (byte)'\n', firstByteToReadIdx, charRead - firstByteToReadIdx); //search in full byte array readed while (separatorIdx >= 0) // at least one '\n' returned { while (firstByteToReadIdx <= separatorIdx) data.Add(buf[firstByteToReadIdx++]); var line = encoding.GetString(data.ToArray()).Trim('\r', '\n'); // convert data to string yield return line; data.Clear(); separatorIdx = Array.IndexOf(buf, (byte)'\n', firstByteToReadIdx, charRead - firstByteToReadIdx); //search in full byte array readed } while (firstByteToReadIdx < charRead) // add all remainings characters to data data.Add(buf[firstByteToReadIdx++]); } } #if ASYNC /// /// Reads a line from the socket asynchronously /// /// The type of encoding used to convert from byte[] to string /// The for this task /// A line from the stream, null if there is nothing to read public async Task ReadLineAsync(System.Text.Encoding encoding, CancellationToken token) { List data = new List(); byte[] buf = new byte[1]; string line = null; while (await ReadAsync(buf, 0, buf.Length, token) > 0) { data.Add(buf[0]); if ((char)buf[0] == '\n') { line = encoding.GetString(data.ToArray()).Trim('\r', '\n'); break; } } return line; } /// /// Reads a line from the socket asynchronously /// /// The type of encoding used to convert from byte[] to string /// A line from the stream, null if there is nothing to read public async Task ReadLineAsync(System.Text.Encoding encoding) { return await ReadLineAsync(encoding, CancellationToken.None); } /// /// Reads all line from the socket /// /// The type of encoding used to convert from byte[] to string /// The size of the buffer /// A list of lines from the stream public async Task> ReadAllLinesAsync(System.Text.Encoding encoding, int bufferSize) { int charRead; List data = new List(); List lines = new List(); byte[] buf = new byte[bufferSize]; while ((charRead = await ReadAsync(buf, 0, buf.Length)) > 0) { var firstByteToReadIdx = 0; var separatorIdx = Array.IndexOf(buf, (byte)'\n', firstByteToReadIdx, charRead - firstByteToReadIdx); //search in full byte array readed while (separatorIdx >= 0) // at least one '\n' returned { while (firstByteToReadIdx <= separatorIdx) data.Add(buf[firstByteToReadIdx++]); var line = encoding.GetString(data.ToArray()).Trim('\r', '\n'); // convert data to string lines.Add(line); data.Clear(); separatorIdx = Array.IndexOf(buf, (byte)'\n', firstByteToReadIdx, charRead - firstByteToReadIdx); //search in full byte array readed } while (firstByteToReadIdx < charRead) // add all remainings characters to data data.Add(buf[firstByteToReadIdx++]); } return lines; } #endif /// /// Writes data to the stream /// /// Buffer to write to stream /// Where in the buffer to start /// Number of bytes to be read public override void Write(byte[] buffer, int offset, int count) { if (BaseStream == null) return; BaseStream.Write(buffer, offset, count); m_lastActivity = DateTime.Now; } #if ASYNC /// /// Writes data to the stream asynchronously /// /// Buffer to write to stream /// Where in the buffer to start /// Number of bytes to be read /// The for this task public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken token) { if (BaseStream == null) return; await BaseStream.WriteAsync(buffer, offset, count, token); m_lastActivity = DateTime.Now; } #endif /// /// Writes a line to the stream using the specified encoding /// /// Encoding used for writing the line /// The data to write public void WriteLine(System.Text.Encoding encoding, string buf) { byte[] data; data = encoding.GetBytes((buf + "\r\n")); Write(data, 0, data.Length); } #if ASYNC /// /// Writes a line to the stream using the specified encoding asynchronously /// /// Encoding used for writing the line /// The data to write /// The for this task public async Task WriteLineAsync(System.Text.Encoding encoding, string buf, CancellationToken token) { byte[] data = encoding.GetBytes(buf + "\r\n"); await WriteAsync(data, 0, data.Length, token); } /// /// Writes a line to the stream using the specified encoding asynchronously /// /// Encoding used for writing the line /// The data to write public async Task WriteLineAsync(System.Text.Encoding encoding, string buf) { await WriteLineAsync(encoding, buf, CancellationToken.None); } #endif #if CORE /// /// Disconnects from server /// public void Close() { Dispose(true); } #endif /// /// Disconnects from server /// protected override void Dispose(bool disposing) { FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Disposing FtpSocketStream..."); if (m_socket != null) { try { if (m_socket.Connected) { //// // Calling Shutdown() with mono causes an // exception if the remote host closed first //m_socket.Shutdown(SocketShutdown.Both); #if CORE m_socket.Dispose(); #else m_socket.Close(); #endif } #if !NET20 && !NET35 m_socket.Dispose(); #endif } catch (SocketException ex) { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Caught and discarded a SocketException while cleaning up the Socket: " + ex.ToString()); } finally { m_socket = null; } } if (m_netStream != null) { try { m_netStream.Dispose(); } catch (IOException ex) { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Caught and discarded an IOException while cleaning up the NetworkStream: " + ex.ToString()); } finally { m_netStream = null; } } #if !NO_SSL if (m_sslStream != null) { try { m_sslStream.Dispose(); } catch (IOException ex) { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Caught and discarded an IOException while cleaning up the SslStream: " + ex.ToString()); } finally { m_sslStream = null; } } #endif } /// /// Sets socket options on the underlying socket /// /// SocketOptionLevel /// SocketOptionName /// SocketOptionValue public void SetSocketOption(SocketOptionLevel level, SocketOptionName name, bool value) { if (m_socket == null) throw new InvalidOperationException("The underlying socket is null. Have you established a connection?"); m_socket.SetSocketOption(level, name, value); } /// /// Connect to the specified host /// /// The host to connect to /// The port to connect to /// Internet Protocol versions to support during the connection phase public void Connect(string host, int port, FtpIpVersion ipVersions) { #if CORE IPAddress[] addresses = Dns.GetHostAddressesAsync(host).Result; #else IAsyncResult ar = null; IPAddress[] addresses = Dns.GetHostAddresses(host); #endif if (ipVersions == 0) throw new ArgumentException("The ipVersions parameter must contain at least 1 flag."); for (int i = 0; i < addresses.Length; i++) { // we don't need to do this check unless // a particular version of IP has been // omitted so we won't. if (ipVersions != FtpIpVersion.ANY) { switch (addresses[i].AddressFamily) { case AddressFamily.InterNetwork: if ((ipVersions & FtpIpVersion.IPv4) != FtpIpVersion.IPv4) { #if DEBUG FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Skipped IPV4 address : " + addresses[i].ToString()); #endif continue; } break; case AddressFamily.InterNetworkV6: if ((ipVersions & FtpIpVersion.IPv6) != FtpIpVersion.IPv6) { #if DEBUG FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Skipped IPV6 address : " + addresses[i].ToString()); #endif continue; } break; } } if (FtpTrace.LogIP) { FtpTrace.WriteStatus(FtpTraceLevel.Info, "Connecting to " + addresses[i].ToString() + ":" + port); } else { FtpTrace.WriteStatus(FtpTraceLevel.Info, "Connecting to ***:" + port); } m_socket = new Socket(addresses[i].AddressFamily, SocketType.Stream, ProtocolType.Tcp); #if CORE m_socket.ConnectAsync(addresses[i], port).Wait(); break; #else ar = m_socket.BeginConnect(addresses[i], port, null, null); if (!ar.AsyncWaitHandle.WaitOne(m_connectTimeout, true)) { Close(); // check to see if we're out of addresses, and throw a TimeoutException if ((i + 1) == addresses.Length) { throw new TimeoutException("Timed out trying to connect!"); } } else { m_socket.EndConnect(ar); // we got a connection, break out // of the loop. break; } #endif } // make sure that we actually connected to // one of the addresses returned from GetHostAddresses() if (m_socket == null || !m_socket.Connected) { Close(); throw new IOException("Failed to connect to host."); } m_netStream = new NetworkStream(m_socket); m_lastActivity = DateTime.Now; } #if ASYNC /// /// Connect to the specified host /// /// The host to connect to /// The port to connect to /// Internet Protocol versions to support during the connection phase public async Task ConnectAsync(string host, int port, FtpIpVersion ipVersions) { IPAddress[] addresses = await Dns.GetHostAddressesAsync(host); if (ipVersions == 0) throw new ArgumentException("The ipVersions parameter must contain at least 1 flag."); for (int i = 0; i < addresses.Length; i++) { // we don't need to do this check unless // a particular version of IP has been // omitted so we won't. if (ipVersions != FtpIpVersion.ANY) { switch (addresses[i].AddressFamily) { case AddressFamily.InterNetwork: if ((ipVersions & FtpIpVersion.IPv4) != FtpIpVersion.IPv4) { #if DEBUG FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Skipped IPV4 address : " + addresses[i].ToString()); #endif continue; } break; case AddressFamily.InterNetworkV6: if ((ipVersions & FtpIpVersion.IPv6) != FtpIpVersion.IPv6) { #if DEBUG FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Skipped IPV6 address : " + addresses[i].ToString()); #endif continue; } break; } } if (FtpTrace.LogIP) { FtpTrace.WriteStatus(FtpTraceLevel.Info, "Connecting to " + addresses[i].ToString() + ":" + port); } else { FtpTrace.WriteStatus(FtpTraceLevel.Info, "Connecting to ***:" + port); } m_socket = new Socket(addresses[i].AddressFamily, SocketType.Stream, ProtocolType.Tcp); #if CORE await m_socket.ConnectAsync(addresses[i], port); break; #else var connectResult = m_socket.BeginConnect(addresses[i], port, null, null); await Task.Factory.FromAsync(connectResult, m_socket.EndConnect); break; #endif } // make sure that we actually connected to // one of the addresses returned from GetHostAddresses() if (m_socket == null || !m_socket.Connected) { Close(); throw new IOException("Failed to connect to host."); } m_netStream = new NetworkStream(m_socket); m_lastActivity = DateTime.Now; } #endif #if !NO_SSL /// /// Activates SSL on this stream using default protocols. Fires the ValidateCertificate event. /// If this event is not handled and there are SslPolicyErrors present, the certificate will /// not be accepted. /// /// The host to authenticate the certificate against public void ActivateEncryption(string targethost) { ActivateEncryption(targethost, null, m_SslProtocols); } #if ASYNC /// /// Activates SSL on this stream using default protocols. Fires the ValidateCertificate event. /// If this event is not handled and there are SslPolicyErrors present, the certificate will /// not be accepted. /// /// The host to authenticate the certificate against public async Task ActivateEncryptionAsync(string targethost) { await ActivateEncryptionAsync(targethost, null, m_SslProtocols); } #endif /// /// Activates SSL on this stream using default protocols. Fires the ValidateCertificate event. /// If this event is not handled and there are SslPolicyErrors present, the certificate will /// not be accepted. /// /// The host to authenticate the certificate against /// A collection of client certificates to use when authenticating the SSL stream public void ActivateEncryption(string targethost, X509CertificateCollection clientCerts) { ActivateEncryption(targethost, clientCerts, m_SslProtocols); } #if ASYNC /// /// Activates SSL on this stream using default protocols. Fires the ValidateCertificate event. /// If this event is not handled and there are SslPolicyErrors present, the certificate will /// not be accepted. /// /// The host to authenticate the certificate against /// A collection of client certificates to use when authenticating the SSL stream public async Task ActivateEncryptionAsync(string targethost, X509CertificateCollection clientCerts) { await ActivateEncryptionAsync(targethost, clientCerts, m_SslProtocols); } #endif /// /// Activates SSL on this stream using the specified protocols. Fires the ValidateCertificate event. /// If this event is not handled and there are SslPolicyErrors present, the certificate will /// not be accepted. /// /// The host to authenticate the certificate against /// A collection of client certificates to use when authenticating the SSL stream /// A bitwise parameter for supported encryption protocols. /// Thrown when authentication fails public void ActivateEncryption(string targethost, X509CertificateCollection clientCerts, SslProtocols sslProtocols) { if (!IsConnected) throw new InvalidOperationException("The FtpSocketStream object is not connected."); if (m_netStream == null) throw new InvalidOperationException("The base network stream is null."); if (m_sslStream != null) throw new InvalidOperationException("SSL Encryption has already been enabled on this stream."); try { DateTime auth_start; TimeSpan auth_time_total; #if CORE m_sslStream = new SslStream(NetworkStream, true, new RemoteCertificateValidationCallback( delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return OnValidateCertificate(certificate, chain, sslPolicyErrors); } )); #else m_sslStream = new FtpSslStream(NetworkStream, true, new RemoteCertificateValidationCallback( delegate (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return OnValidateCertificate(certificate, chain, sslPolicyErrors); } )); #endif auth_start = DateTime.Now; #if CORE m_sslStream.AuthenticateAsClientAsync(targethost, clientCerts, sslProtocols, true).Wait(); #else m_sslStream.AuthenticateAsClient(targethost, clientCerts, sslProtocols, true); #endif auth_time_total = DateTime.Now.Subtract(auth_start); FtpTrace.WriteStatus(FtpTraceLevel.Info, "FTPS Authentication Successful"); FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Time to activate encryption: " + auth_time_total.Hours + "h " + auth_time_total.Minutes + "m " + auth_time_total.Seconds + "s. Total Seconds: " + auth_time_total.TotalSeconds + "."); } catch (AuthenticationException) { // authentication failed and in addition it left our // ssl stream in an unusable state so cleanup needs // to be done and the exception can be re-thrown for // handling down the chain. (Add logging?) Close(); FtpTrace.WriteStatus(FtpTraceLevel.Error, "FTPS Authentication Failed"); throw; } } #if ASYNC /// /// Activates SSL on this stream using the specified protocols. Fires the ValidateCertificate event. /// If this event is not handled and there are SslPolicyErrors present, the certificate will /// not be accepted. /// /// The host to authenticate the certificate against /// A collection of client certificates to use when authenticating the SSL stream /// A bitwise parameter for supported encryption protocols. /// Thrown when authentication fails public async Task ActivateEncryptionAsync(string targethost, X509CertificateCollection clientCerts, SslProtocols sslProtocols) { if (!IsConnected) throw new InvalidOperationException("The FtpSocketStream object is not connected."); if (m_netStream == null) throw new InvalidOperationException("The base network stream is null."); if (m_sslStream != null) throw new InvalidOperationException("SSL Encryption has already been enabled on this stream."); try { DateTime auth_start; TimeSpan auth_time_total; #if CORE m_sslStream = new SslStream(NetworkStream, true, new RemoteCertificateValidationCallback( delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return OnValidateCertificate(certificate, chain, sslPolicyErrors); } )); #else m_sslStream = new FtpSslStream(NetworkStream, true, new RemoteCertificateValidationCallback( delegate (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return OnValidateCertificate(certificate, chain, sslPolicyErrors); } )); #endif auth_start = DateTime.Now; await m_sslStream.AuthenticateAsClientAsync(targethost, clientCerts, sslProtocols, true); auth_time_total = DateTime.Now.Subtract(auth_start); FtpTrace.WriteStatus(FtpTraceLevel.Info, "FTPS Authentication Successful"); FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Time to activate encryption: " + auth_time_total.Hours + "h " + auth_time_total.Minutes + "m " + auth_time_total.Seconds + "s. Total Seconds: " + auth_time_total.TotalSeconds + "."); } catch (AuthenticationException) { // authentication failed and in addition it left our // ssl stream in an unusable state so cleanup needs // to be done and the exception can be re-thrown for // handling down the chain. (Add logging?) Close(); FtpTrace.WriteStatus(FtpTraceLevel.Error, "FTPS Authentication Failed"); throw; } } #endif #endif #if !CORE /// /// Deactivates SSL on this stream using the specified protocols and reverts back to plain-text FTP. /// public void DeactivateEncryption() { if (!IsConnected) throw new InvalidOperationException("The FtpSocketStream object is not connected."); if (m_sslStream == null) throw new InvalidOperationException("SSL Encryption has not been enabled on this stream."); m_sslStream.Close(); m_sslStream = null; } #endif /// /// Instructs this stream to listen for connections on the specified address and port /// /// The address to listen on /// The port to listen on public void Listen(IPAddress address, int port) { if (!IsConnected) { if (m_socket == null) m_socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); m_socket.Bind(new IPEndPoint(address, port)); m_socket.Listen(1); } } /// /// Accepts a connection from a listening socket /// public void Accept() { if (m_socket != null) m_socket = m_socket.Accept(); } #if NET45 /// /// Accepts a connection from a listening socket /// public async Task AcceptAsync() { if (m_socket!=null) { var iar = m_socket.BeginAccept(null, null); await Task.Factory.FromAsync(iar, m_socket.EndAccept); } } #endif #if ASYNC && !NET45 /// /// Accepts a connection from a listening socket /// public async Task AcceptAsync() { if (m_socket != null) { m_socket = await m_socket.AcceptAsync(); #if CORE m_netStream = new NetworkStream(m_socket); #endif } } #else /// /// Asynchronously accepts a connection from a listening socket /// /// /// /// public IAsyncResult BeginAccept(AsyncCallback callback, object state) { if (m_socket != null) return m_socket.BeginAccept(callback, state); return null; } /// /// Completes a BeginAccept() operation /// /// IAsyncResult returned from BeginAccept public void EndAccept(IAsyncResult ar) { if (m_socket != null) { m_socket = m_socket.EndAccept(ar); m_netStream = new NetworkStream(m_socket); } } #endif } }