/// <summary> /// 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. /// </summary> /// <param name="targethost">The host to authenticate the certificate against</param> /// <param name="clientCerts">A collection of client certificates to use when authenticating the SSL stream</param> /// <param name="sslProtocols">A bitwise parameter for supported encryption protocols.</param> /// <exception cref="AuthenticationException">Thrown when authentication fails</exception> 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; } }
public override void Close() { #endif 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 !NET2 && !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 #if CORE base.Dispose(); #endif }
/// <summary> /// Gets the hash of an object on the server using the currently selected hash algorithm. /// </summary> /// <remarks> /// Supported algorithms, if any, are available in the <see cref="HashAlgorithms"/> /// property. You should confirm that it's not equal /// to <see cref="FtpHashAlgorithm.NONE"/> before calling this method /// otherwise the server trigger a <see cref="FtpCommandException"/> /// due to a lack of support for the HASH command. You can /// set the algorithm using the <see cref="SetHashAlgorithm"/> method and /// you can query the server for the current hash algorithm /// using the <see cref="GetHashAlgorithm"/> method. /// /// This feature is experimental and based on the following draft: /// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02 /// </remarks> /// <param name="path">Full or relative path of the object to compute the hash for.</param> /// <returns>The hash of the file.</returns> /// <exception cref="FtpCommandException"> /// Thrown if the <see cref="HashAlgorithms"/> property is <see cref="FtpHashAlgorithm.NONE"/>, /// the remote path does not exist, or the command cannot be executed. /// </exception> /// <exception cref="ArgumentException">Path argument is null</exception> /// <exception cref="NotImplementedException">Thrown when an unknown hash algorithm type is returned by the server</exception> /// <example><code source="..\Examples\GetHash.cs" lang="cs" /></example> public FtpHash GetHash(string path) { FtpReply reply; FtpHash hash = new FtpHash(); Match m; if (path == null) { throw new ArgumentException("GetHash(path) argument can't be null"); } #if !CORE14 lock (m_lock) { #endif if (!(reply = Execute("HASH " + path.GetFtpPath())).Success) { throw new FtpCommandException(reply); } #if !CORE14 } #endif // Current draft says the server should return this: // SHA-256 0-49 169cd22282da7f147cb491e559e9dd filename.ext if (!(m = Regex.Match(reply.Message, @"(?<algorithm>.+)\s" + @"(?<bytestart>\d+)-(?<byteend>\d+)\s" + @"(?<hash>.+)\s" + @"(?<filename>.+)")).Success) { // Current version of FileZilla returns this: // SHA-1 21c2ca15cf570582949eb59fb78038b9c27ffcaf m = Regex.Match(reply.Message, @"(?<algorithm>.+)\s(?<hash>.+)\s"); } if (m != null && m.Success) { switch (m.Groups["algorithm"].Value) { case "SHA-1": hash.Algorithm = FtpHashAlgorithm.SHA1; break; case "SHA-256": hash.Algorithm = FtpHashAlgorithm.SHA256; break; case "SHA-512": hash.Algorithm = FtpHashAlgorithm.SHA512; break; case "MD5": hash.Algorithm = FtpHashAlgorithm.MD5; break; default: throw new NotImplementedException("Unknown hash algorithm: " + m.Groups["algorithm"].Value); } hash.Value = m.Groups["hash"].Value; } else { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to parse hash from: " + reply.Message); } return(hash); }
/// <summary> /// Connect to the specified host /// </summary> /// <param name="host">The host to connect to</param> /// <param name="port">The port to connect to</param> /// <param name="ipVersions">Internet Protocol versions to support during the connection phase</param> 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(); #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; }
/// <summary> /// Disposes the stream /// </summary> public new void Dispose() { FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Disposing FtpSocketStream..."); Close(); }
/// <summary> /// Gets a file listing from the server. Each <see cref="FtpListItem"/> object returned /// contains information about the file that was able to be retrieved. /// </summary> /// <remarks> /// If a <see cref="DateTime"/> property is equal to <see cref="DateTime.MinValue"/> then it means the /// date in question was not able to be retrieved. If the <see cref="FtpListItem.Size"/> property /// is equal to 0, then it means the size of the object could also not /// be retrieved. /// </remarks> /// <param name="path">The path of the directory to list</param> /// <param name="options">Options that dictacte how a list is performed and what information is gathered.</param> /// <returns>An array of FtpListItem objects</returns> /// <example><code source="..\Examples\GetListing.cs" lang="cs" /></example> public FtpListItem[] GetListing(string path, FtpListOption options) { FtpTrace.WriteFunc("GetListing", new object[] { path, options }); FtpListItem item = null; List <FtpListItem> lst = new List <FtpListItem>(); List <string> rawlisting = new List <string>(); string listcmd = null; string buf = null; // read flags bool isIncludeSelf = (options & FtpListOption.IncludeSelfAndParent) == FtpListOption.IncludeSelfAndParent; bool isForceList = (options & FtpListOption.ForceList) == FtpListOption.ForceList; bool isNoPath = (options & FtpListOption.NoPath) == FtpListOption.NoPath; bool isNameList = (options & FtpListOption.NameList) == FtpListOption.NameList; bool isUseLS = (options & FtpListOption.UseLS) == FtpListOption.UseLS; bool isAllFiles = (options & FtpListOption.AllFiles) == FtpListOption.AllFiles; bool isRecursive = (options & FtpListOption.Recursive) == FtpListOption.Recursive && RecursiveList; bool isDerefLinks = (options & FtpListOption.DerefLinks) == FtpListOption.DerefLinks; bool isGetModified = (options & FtpListOption.Modify) == FtpListOption.Modify; bool isGetSize = (options & FtpListOption.Size) == FtpListOption.Size; // calc path to request path = GetAbsolutePath(path); // MLSD provides a machine readable format with 100% accurate information // so always prefer MLSD over LIST unless the caller of this method overrides it with the ForceList option bool machineList = false; if ((!isForceList || m_parser == FtpParser.Machine) && HasFeature(FtpCapability.MLSD)) { listcmd = "MLSD"; machineList = true; } else { if (isUseLS) { listcmd = "LS"; } else if (isNameList) { listcmd = "NLST"; } else { string listopts = ""; listcmd = "LIST"; if (isAllFiles) { listopts += "a"; } if (isRecursive) { listopts += "R"; } if (listopts.Length > 0) { listcmd += " -" + listopts; } } } if (!isNoPath) { listcmd = (listcmd + " " + path.GetFtpPath()); } #if !CORE14 lock (m_lock) { #endif Execute("TYPE I"); // read in raw file listing using (FtpDataStream stream = OpenDataStream(listcmd, 0)) { try { FtpTrace.WriteLine(FtpTraceLevel.Verbose, "+---------------------------------------+"); while ((buf = stream.ReadLine(Encoding)) != null) { if (buf.Length > 0) { rawlisting.Add(buf); FtpTrace.WriteLine(FtpTraceLevel.Verbose, "Listing: " + buf); } } FtpTrace.WriteLine(FtpTraceLevel.Verbose, "-----------------------------------------"); } finally { stream.Close(); } } #if !CORE14 } #endif for (int i = 0; i < rawlisting.Count; i++) { buf = rawlisting[i]; if (isNameList) { // if NLST was used we only have a file name so // there is nothing to parse. item = new FtpListItem() { FullName = buf }; if (DirectoryExists(item.FullName)) { item.Type = FtpFileSystemObjectType.Directory; } else { item.Type = FtpFileSystemObjectType.File; } lst.Add(item); } else { // if this is a result of LIST -R then the path will be spit out // before each block of objects if (listcmd.StartsWith("LIST") && isRecursive) { if (buf.StartsWith("/") && buf.EndsWith(":")) { path = buf.TrimEnd(':'); continue; } } // if the next line in the listing starts with spaces // it is assumed to be a continuation of the current line if (i + 1 < rawlisting.Count && (rawlisting[i + 1].StartsWith("\t") || rawlisting[i + 1].StartsWith(" "))) { buf += rawlisting[++i]; } item = m_listParser.ParseSingleLine(path, buf, m_caps, machineList); // FtpListItem.Parse() returns null if the line // could not be parsed if (item != null) { if (isIncludeSelf || !(item.Name == "." || item.Name == "..")) { lst.Add(item); } else { //FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Skipped self or parent item: " + item.Name); } } else { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to parse file listing: " + buf); } } // load extended information that wasn't available if the list options flags say to do so. if (item != null) { // try to dereference symbolic links if the appropriate list // option was passed if (item.Type == FtpFileSystemObjectType.Link && isDerefLinks) { item.LinkObject = DereferenceLink(item); } // if need to get file modified date if (isGetModified && HasFeature(FtpCapability.MDTM)) { // if the modified date was not loaded or the modified date is more than a day in the future // and the server supports the MDTM command, load the modified date. // most servers do not support retrieving the modified date // of a directory but we try any way. if (item.Modified == DateTime.MinValue || listcmd.StartsWith("LIST")) { DateTime modify; if (item.Type == FtpFileSystemObjectType.Directory) { FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Trying to retrieve modification time of a directory, some servers don't like this..."); } if ((modify = GetModifiedTime(item.FullName)) != DateTime.MinValue) { item.Modified = modify; } } } // if need to get file size if (isGetSize && HasFeature(FtpCapability.SIZE)) { // if no size was parsed, the object is a file and the server // supports the SIZE command, then load the file size if (item.Size == -1) { if (item.Type != FtpFileSystemObjectType.Directory) { item.Size = GetFileSize(item.FullName); } else { item.Size = 0; } } } } } return(lst.ToArray()); }
/// <summary> /// Returns information about a file system object. Returns null if the server response can't /// be parsed or the server returns a failure completion code. The error for a failure /// is logged with FtpTrace. No exception is thrown on error because that would negate /// the usefulness of this method for checking for the existence of an object. /// </summary> /// <param name="path">The path of the file or folder</param> /// <param name="dateModified">Get the accurate modified date using another MDTM command</param> /// <returns>A FtpListItem object</returns> public FtpListItem GetObjectInfo(string path, bool dateModified = false) { FtpTrace.WriteFunc("GetObjectInfo", new object[] { path, dateModified }); FtpReply reply; string[] res; bool supportsMachineList = (Capabilities & FtpCapability.MLSD) == FtpCapability.MLSD; FtpListItem result = null; if (supportsMachineList) { // USE MACHINE LISTING TO GET INFO FOR A SINGLE FILE if ((reply = Execute("MLST " + path)).Success) { res = reply.InfoMessages.Split('\n'); if (res.Length > 1) { string info = ""; for (int i = 1; i < res.Length; i++) { info += res[i]; } result = m_listParser.ParseSingleLine(null, info, m_caps, true); } } else { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to get object info for path " + path + " with error " + reply.ErrorMessage); } } else { // USE GETLISTING TO GET ALL FILES IN DIR .. SLOWER BUT AT LEAST IT WORKS string dirPath = path.GetFtpDirectoryName(); FtpListItem[] dirItems = GetListing(dirPath); foreach (var dirItem in dirItems) { if (dirItem.FullName == path) { result = dirItem; break; } } FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to get object info for path " + path + " since MLST not supported and GetListing() fails to list file/folder."); } // Get the accurate date modified using another MDTM command if (result != null && dateModified && HasFeature(FtpCapability.MDTM)) { result.Modified = GetModifiedTime(path); } return(result); }
/// <summary> /// Connect to the specified host /// </summary> /// <param name="host">The host to connect to</param> /// <param name="port">The port to connect to</param> /// <param name="ipVersions">Internet Protocol versions to support during the connection phase</param> 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; }
/// <summary> /// Disconnects from server /// </summary> protected override void Dispose(bool disposing) { FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Disposing FtpSocketStream..."); #if !NO_SSL if (m_bufStream != null) { // ensure the last of the buffered bytes are flushed // before we close the socket and network stream m_bufStream.Flush(); } #endif 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; } } if (m_bufStream != null) { try { m_bufStream.Dispose(); } catch (IOException ex) { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Caught and discarded an IOException while cleaning up the BufferedStream: " + ex.ToString()); } finally { m_bufStream = null; } } #endif }