FluentFTP 추가
This commit is contained in:
189
FluentFTP/Stream/FtpDataStream.cs
Normal file
189
FluentFTP/Stream/FtpDataStream.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
#if ASYNC
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
|
||||
namespace FluentFTP {
|
||||
|
||||
/// <summary>
|
||||
/// Base class for data stream connections
|
||||
/// </summary>
|
||||
public class FtpDataStream : FtpSocketStream {
|
||||
FtpReply m_commandStatus;
|
||||
/// <summary>
|
||||
/// Gets the status of the command that was used to open
|
||||
/// this data channel
|
||||
/// </summary>
|
||||
public FtpReply CommandStatus {
|
||||
get {
|
||||
return m_commandStatus;
|
||||
}
|
||||
set {
|
||||
m_commandStatus = value;
|
||||
}
|
||||
}
|
||||
|
||||
FtpClient m_control = null;
|
||||
/// <summary>
|
||||
/// Gets or sets the control connection for this data stream. Setting
|
||||
/// the control connection causes the object to be cloned and a new
|
||||
/// connection is made to the server to carry out the task. This ensures
|
||||
/// that multiple streams can be opened simultaneously.
|
||||
/// </summary>
|
||||
public FtpClient ControlConnection {
|
||||
get {
|
||||
return m_control;
|
||||
}
|
||||
set {
|
||||
m_control = value;
|
||||
}
|
||||
}
|
||||
|
||||
long m_length = 0;
|
||||
/// <summary>
|
||||
/// Gets or sets the length of the stream. Only valid for file transfers
|
||||
/// and only valid on servers that support the Size command.
|
||||
/// </summary>
|
||||
public override long Length {
|
||||
get {
|
||||
return m_length;
|
||||
}
|
||||
}
|
||||
|
||||
long m_position = 0;
|
||||
/// <summary>
|
||||
/// Gets or sets the position of the stream
|
||||
/// </summary>
|
||||
public override long Position {
|
||||
get {
|
||||
return m_position;
|
||||
}
|
||||
set {
|
||||
throw new InvalidOperationException("You cannot modify the position of a FtpDataStream. This property is updated as data is read or written to the stream.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data off the stream
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to read into</param>
|
||||
/// <param name="offset">Where to start in the buffer</param>
|
||||
/// <param name="count">Number of bytes to read</param>
|
||||
/// <returns>The number of bytes read</returns>
|
||||
public override int Read(byte[] buffer, int offset, int count) {
|
||||
int read = base.Read(buffer, offset, count);
|
||||
m_position += read;
|
||||
return read;
|
||||
}
|
||||
|
||||
#if ASYNC
|
||||
/// <summary>
|
||||
/// Reads data off the stream asynchronously
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to read into</param>
|
||||
/// <param name="offset">Where to start in the buffer</param>
|
||||
/// <param name="count">Number of bytes to read</param>
|
||||
/// <param name="token">The cancellation token for this task</param>
|
||||
/// <returns>The number of bytes read</returns>
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken token) {
|
||||
int read = await base.ReadAsync(buffer, offset, count, token);
|
||||
m_position += read;
|
||||
return read;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to the stream
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to write to the stream</param>
|
||||
/// <param name="offset">Where to start in the buffer</param>
|
||||
/// <param name="count">The number of bytes to write to the buffer</param>
|
||||
public override void Write(byte[] buffer, int offset, int count) {
|
||||
base.Write(buffer, offset, count);
|
||||
m_position += count;
|
||||
}
|
||||
|
||||
#if ASYNC
|
||||
/// <summary>
|
||||
/// Writes data to the stream asynchronously
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to write to the stream</param>
|
||||
/// <param name="offset">Where to start in the buffer</param>
|
||||
/// <param name="count">The number of bytes to write to the buffer</param>
|
||||
/// <param name="token">The <see cref="CancellationToken"/> for this task</param>
|
||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken token) {
|
||||
await base.WriteAsync(buffer, offset, count, token);
|
||||
m_position += count;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Sets the length of this stream
|
||||
/// </summary>
|
||||
/// <param name="value">Value to apply to the Length property</param>
|
||||
public override void SetLength(long value) {
|
||||
m_length = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the position of the stream. Intended to be used
|
||||
/// internally by FtpControlConnection.
|
||||
/// </summary>
|
||||
/// <param name="pos">The position</param>
|
||||
public void SetPosition(long pos) {
|
||||
m_position = pos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the connection and reads the server's reply
|
||||
/// </summary>
|
||||
public new FtpReply Close() {
|
||||
base.Close();
|
||||
|
||||
try {
|
||||
if (ControlConnection != null)
|
||||
return ControlConnection.CloseDataStream(this);
|
||||
} finally {
|
||||
m_commandStatus = new FtpReply();
|
||||
m_control = null;
|
||||
}
|
||||
|
||||
return new FtpReply();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new data stream object
|
||||
/// </summary>
|
||||
/// <param name="conn">The control connection to be used for carrying out this operation</param>
|
||||
public FtpDataStream(FtpClient conn) : base(conn.SslProtocols) {
|
||||
if (conn == null)
|
||||
throw new ArgumentException("The control connection cannot be null.");
|
||||
|
||||
ControlConnection = conn;
|
||||
// always accept certificate no matter what because if code execution ever
|
||||
// gets here it means the certificate on the control connection object being
|
||||
// cloned was already accepted.
|
||||
ValidateCertificate += new FtpSocketStreamSslValidation(delegate (FtpSocketStream obj, FtpSslValidationEventArgs e) {
|
||||
e.Accept = true;
|
||||
});
|
||||
|
||||
m_position = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizer
|
||||
/// </summary>
|
||||
~FtpDataStream() {
|
||||
try {
|
||||
Dispose(false);
|
||||
} catch (Exception ex) {
|
||||
FtpTrace.WriteLine(FtpTraceLevel.Warn, "[Finalizer] Caught and discarded an exception while disposing the FtpDataStream: " + ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1158
FluentFTP/Stream/FtpSocketStream.cs
Normal file
1158
FluentFTP/Stream/FtpSocketStream.cs
Normal file
File diff suppressed because it is too large
Load Diff
284
FluentFTP/Stream/FtpSslStream.cs
Normal file
284
FluentFTP/Stream/FtpSslStream.cs
Normal file
@@ -0,0 +1,284 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if !CORE
|
||||
using System.Linq;
|
||||
using System.Net.Security;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
#endif
|
||||
|
||||
namespace FluentFTP {
|
||||
|
||||
#if !CORE
|
||||
/// <summary>
|
||||
/// .NET SslStream doesn't close TLS connection properly.
|
||||
/// It does not send the close_notify alert before closing the connection.
|
||||
/// FtpSslStream uses unsafe code to do that.
|
||||
/// This is required when we want to downgrade the connection to plaintext using CCC command.
|
||||
/// Thanks to Neco @ https://stackoverflow.com/questions/237807/net-sslstream-doesnt-close-tls-connection-properly/22626756#22626756
|
||||
/// </summary>
|
||||
internal class FtpSslStream : SslStream {
|
||||
|
||||
private bool sentCloseNotify = false;
|
||||
|
||||
public FtpSslStream(Stream innerStream)
|
||||
: base(innerStream) {
|
||||
}
|
||||
public FtpSslStream(Stream innerStream, bool leaveInnerStreamOpen)
|
||||
: base(innerStream, leaveInnerStreamOpen) {
|
||||
}
|
||||
public FtpSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback)
|
||||
: base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback) {
|
||||
}
|
||||
public FtpSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback, LocalCertificateSelectionCallback userCertificateSelectionCallback)
|
||||
: base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback, userCertificateSelectionCallback) {
|
||||
}
|
||||
#if !NET20 && !NET35
|
||||
public FtpSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback, LocalCertificateSelectionCallback userCertificateSelectionCallback, EncryptionPolicy encryptionPolicy)
|
||||
: base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback, userCertificateSelectionCallback, encryptionPolicy) {
|
||||
}
|
||||
#endif
|
||||
public override void Close() {
|
||||
try {
|
||||
if (!sentCloseNotify) {
|
||||
SslDirectCall.CloseNotify(this);
|
||||
sentCloseNotify = true;
|
||||
}
|
||||
} finally {
|
||||
base.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe static class SslDirectCall {
|
||||
/// <summary>
|
||||
/// Send an SSL close_notify alert.
|
||||
/// </summary>
|
||||
/// <param name="sslStream"></param>
|
||||
public static void CloseNotify(SslStream sslStream) {
|
||||
if (sslStream.IsAuthenticated && sslStream.CanWrite) {
|
||||
bool isServer = sslStream.IsServer;
|
||||
|
||||
byte[] result;
|
||||
int resultSz;
|
||||
var asmbSystem = typeof(System.Net.Authorization).Assembly;
|
||||
|
||||
int SCHANNEL_SHUTDOWN = 1;
|
||||
var workArray = BitConverter.GetBytes(SCHANNEL_SHUTDOWN);
|
||||
|
||||
var sslstate = FtpReflection.GetField(sslStream, "_SslState");
|
||||
var context = FtpReflection.GetProperty(sslstate, "Context");
|
||||
|
||||
var securityContext = FtpReflection.GetField(context, "m_SecurityContext");
|
||||
var securityContextHandleOriginal = FtpReflection.GetField(securityContext, "_handle");
|
||||
SslNativeApi.SSPIHandle securityContextHandle = default(SslNativeApi.SSPIHandle);
|
||||
securityContextHandle.HandleHi = (IntPtr)FtpReflection.GetField(securityContextHandleOriginal, "HandleHi");
|
||||
securityContextHandle.HandleLo = (IntPtr)FtpReflection.GetField(securityContextHandleOriginal, "HandleLo");
|
||||
|
||||
var credentialsHandle = FtpReflection.GetField(context, "m_CredentialsHandle");
|
||||
var credentialsHandleHandleOriginal = FtpReflection.GetField(credentialsHandle, "_handle");
|
||||
SslNativeApi.SSPIHandle credentialsHandleHandle = default(SslNativeApi.SSPIHandle);
|
||||
credentialsHandleHandle.HandleHi = (IntPtr)FtpReflection.GetField(credentialsHandleHandleOriginal, "HandleHi");
|
||||
credentialsHandleHandle.HandleLo = (IntPtr)FtpReflection.GetField(credentialsHandleHandleOriginal, "HandleLo");
|
||||
|
||||
int bufferSize = 1;
|
||||
SslNativeApi.SecurityBufferDescriptor securityBufferDescriptor = new SslNativeApi.SecurityBufferDescriptor(bufferSize);
|
||||
SslNativeApi.SecurityBufferStruct[] unmanagedBuffer = new SslNativeApi.SecurityBufferStruct[bufferSize];
|
||||
|
||||
fixed (SslNativeApi.SecurityBufferStruct* ptr = unmanagedBuffer)
|
||||
fixed (void* workArrayPtr = workArray) {
|
||||
securityBufferDescriptor.UnmanagedPointer = (void*)ptr;
|
||||
|
||||
unmanagedBuffer[0].token = (IntPtr)workArrayPtr;
|
||||
unmanagedBuffer[0].count = workArray.Length;
|
||||
unmanagedBuffer[0].type = SslNativeApi.BufferType.Token;
|
||||
|
||||
SslNativeApi.SecurityStatus status;
|
||||
status = (SslNativeApi.SecurityStatus)SslNativeApi.ApplyControlToken(ref securityContextHandle, securityBufferDescriptor);
|
||||
if (status == SslNativeApi.SecurityStatus.OK) {
|
||||
unmanagedBuffer[0].token = IntPtr.Zero;
|
||||
unmanagedBuffer[0].count = 0;
|
||||
unmanagedBuffer[0].type = SslNativeApi.BufferType.Token;
|
||||
|
||||
SslNativeApi.SSPIHandle contextHandleOut = default(SslNativeApi.SSPIHandle);
|
||||
SslNativeApi.ContextFlags outflags = SslNativeApi.ContextFlags.Zero;
|
||||
long ts = 0;
|
||||
|
||||
var inflags = SslNativeApi.ContextFlags.SequenceDetect |
|
||||
SslNativeApi.ContextFlags.ReplayDetect |
|
||||
SslNativeApi.ContextFlags.Confidentiality |
|
||||
SslNativeApi.ContextFlags.AcceptExtendedError |
|
||||
SslNativeApi.ContextFlags.AllocateMemory |
|
||||
SslNativeApi.ContextFlags.InitStream;
|
||||
|
||||
if (isServer) {
|
||||
status = (SslNativeApi.SecurityStatus)SslNativeApi.AcceptSecurityContext(ref credentialsHandleHandle, ref securityContextHandle, null,
|
||||
inflags, SslNativeApi.Endianness.Native, ref contextHandleOut, securityBufferDescriptor, ref outflags, out ts);
|
||||
} else {
|
||||
status = (SslNativeApi.SecurityStatus)SslNativeApi.InitializeSecurityContextW(ref credentialsHandleHandle, ref securityContextHandle, null,
|
||||
inflags, 0, SslNativeApi.Endianness.Native, null, 0, ref contextHandleOut, securityBufferDescriptor, ref outflags, out ts);
|
||||
}
|
||||
if (status == SslNativeApi.SecurityStatus.OK) {
|
||||
byte[] resultArr = new byte[unmanagedBuffer[0].count];
|
||||
Marshal.Copy(unmanagedBuffer[0].token, resultArr, 0, resultArr.Length);
|
||||
Marshal.FreeCoTaskMem(unmanagedBuffer[0].token);
|
||||
result = resultArr;
|
||||
resultSz = resultArr.Length;
|
||||
} else {
|
||||
throw new InvalidOperationException(string.Format("AcceptSecurityContext/InitializeSecurityContextW returned [{0}] during CloseNotify.", status));
|
||||
}
|
||||
} else {
|
||||
throw new InvalidOperationException(string.Format("ApplyControlToken returned [{0}] during CloseNotify.", status));
|
||||
}
|
||||
}
|
||||
|
||||
var innerStream = (Stream)FtpReflection.GetProperty(sslstate, "InnerStream");
|
||||
innerStream.Write(result, 0, resultSz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe static class SslNativeApi {
|
||||
internal enum BufferType {
|
||||
Empty,
|
||||
Data,
|
||||
Token,
|
||||
Parameters,
|
||||
Missing,
|
||||
Extra,
|
||||
Trailer,
|
||||
Header,
|
||||
Padding = 9,
|
||||
Stream,
|
||||
ChannelBindings = 14,
|
||||
TargetHost = 16,
|
||||
ReadOnlyFlag = -2147483648,
|
||||
ReadOnlyWithChecksum = 268435456
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
internal struct SSPIHandle {
|
||||
public IntPtr HandleHi;
|
||||
public IntPtr HandleLo;
|
||||
public bool IsZero {
|
||||
get {
|
||||
return this.HandleHi == IntPtr.Zero && this.HandleLo == IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
|
||||
internal void SetToInvalid() {
|
||||
this.HandleHi = IntPtr.Zero;
|
||||
this.HandleLo = IntPtr.Zero;
|
||||
}
|
||||
public override string ToString() {
|
||||
return this.HandleHi.ToString("x") + ":" + this.HandleLo.ToString("x");
|
||||
}
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal class SecurityBufferDescriptor {
|
||||
public readonly int Version;
|
||||
public readonly int Count;
|
||||
public unsafe void* UnmanagedPointer;
|
||||
public SecurityBufferDescriptor(int count) {
|
||||
this.Version = 0;
|
||||
this.Count = count;
|
||||
this.UnmanagedPointer = null;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct SecurityBufferStruct {
|
||||
public int count;
|
||||
public BufferType type;
|
||||
public IntPtr token;
|
||||
public static readonly int Size = sizeof(SecurityBufferStruct);
|
||||
}
|
||||
|
||||
internal enum SecurityStatus {
|
||||
OK,
|
||||
ContinueNeeded = 590610,
|
||||
CompleteNeeded,
|
||||
CompAndContinue,
|
||||
ContextExpired = 590615,
|
||||
CredentialsNeeded = 590624,
|
||||
Renegotiate,
|
||||
OutOfMemory = -2146893056,
|
||||
InvalidHandle,
|
||||
Unsupported,
|
||||
TargetUnknown,
|
||||
InternalError,
|
||||
PackageNotFound,
|
||||
NotOwner,
|
||||
CannotInstall,
|
||||
InvalidToken,
|
||||
CannotPack,
|
||||
QopNotSupported,
|
||||
NoImpersonation,
|
||||
LogonDenied,
|
||||
UnknownCredentials,
|
||||
NoCredentials,
|
||||
MessageAltered,
|
||||
OutOfSequence,
|
||||
NoAuthenticatingAuthority,
|
||||
IncompleteMessage = -2146893032,
|
||||
IncompleteCredentials = -2146893024,
|
||||
BufferNotEnough,
|
||||
WrongPrincipal,
|
||||
TimeSkew = -2146893020,
|
||||
UntrustedRoot,
|
||||
IllegalMessage,
|
||||
CertUnknown,
|
||||
CertExpired,
|
||||
AlgorithmMismatch = -2146893007,
|
||||
SecurityQosFailed,
|
||||
SmartcardLogonRequired = -2146892994,
|
||||
UnsupportedPreauth = -2146892989,
|
||||
BadBinding = -2146892986
|
||||
}
|
||||
[Flags]
|
||||
internal enum ContextFlags {
|
||||
Zero = 0,
|
||||
Delegate = 1,
|
||||
MutualAuth = 2,
|
||||
ReplayDetect = 4,
|
||||
SequenceDetect = 8,
|
||||
Confidentiality = 16,
|
||||
UseSessionKey = 32,
|
||||
AllocateMemory = 256,
|
||||
Connection = 2048,
|
||||
InitExtendedError = 16384,
|
||||
AcceptExtendedError = 32768,
|
||||
InitStream = 32768,
|
||||
AcceptStream = 65536,
|
||||
InitIntegrity = 65536,
|
||||
AcceptIntegrity = 131072,
|
||||
InitManualCredValidation = 524288,
|
||||
InitUseSuppliedCreds = 128,
|
||||
InitIdentify = 131072,
|
||||
AcceptIdentify = 524288,
|
||||
ProxyBindings = 67108864,
|
||||
AllowMissingBindings = 268435456,
|
||||
UnverifiedTargetName = 536870912
|
||||
}
|
||||
internal enum Endianness {
|
||||
Network,
|
||||
Native = 16
|
||||
}
|
||||
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||||
[DllImport("secur32.dll", ExactSpelling = true, SetLastError = true)]
|
||||
internal static extern int ApplyControlToken(ref SSPIHandle contextHandle, [In] [Out] SecurityBufferDescriptor outputBuffer);
|
||||
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||||
[DllImport("secur32.dll", ExactSpelling = true, SetLastError = true)]
|
||||
internal unsafe static extern int AcceptSecurityContext(ref SSPIHandle credentialHandle, ref SSPIHandle contextHandle, [In] SecurityBufferDescriptor inputBuffer, [In] ContextFlags inFlags, [In] Endianness endianness, ref SSPIHandle outContextPtr, [In] [Out] SecurityBufferDescriptor outputBuffer, [In] [Out] ref ContextFlags attributes, out long timeStamp);
|
||||
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||||
[DllImport("secur32.dll", ExactSpelling = true, SetLastError = true)]
|
||||
internal unsafe static extern int InitializeSecurityContextW(ref SSPIHandle credentialHandle, ref SSPIHandle contextHandle, [In] byte* targetName, [In] ContextFlags inFlags, [In] int reservedI, [In] Endianness endianness, [In] SecurityBufferDescriptor inputBuffer, [In] int reservedII, ref SSPIHandle outContextPtr, [In] [Out] SecurityBufferDescriptor outputBuffer, [In] [Out] ref ContextFlags attributes, out long timeStamp);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user