sources/Google.Solutions.Ssh/Native/NativeMethods.cs (653 lines of code) (raw):

// // Copyright 2020 Google LLC // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // using Google.Solutions.Common.Util; using Microsoft.Win32.SafeHandles; using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Text; #if DEBUG using System.Threading; #endif #pragma warning disable CA2101 // Specify marshaling for P/Invoke string arguments #pragma warning disable CA1419 // Provide a parameterless constructor #pragma warning disable IDE0049 // Simplify Names namespace Google.Solutions.Ssh.Native { public enum LIBSSH2_HOSTKEY_HASH : Int32 { MD5 = 1, SHA1 = 2, SHA256 = 3 } internal enum LIBSSH2_METHOD : Int32 { KEX = 0, HOSTKEY = 1, CRYPT_CS = 2, // Client -> Server CRYPT_SC = 3, // Server -> Client MAC_CS = 4, // Client -> Server MAC_SC = 5, // Server -> Client COMP_CS = 6, // Compression Client -> Server COMP_SC = 7, // Compression Server -> Client LANG_CS = 8, // Client -> Server LANG_SC = 9, // Server -> Client } [Flags] [SuppressMessage("Naming", "CA1714:Flags enums should have plural names")] internal enum LIBSSH2_TRACE : Int32 { TRANS = (1 << 1), KEX = (1 << 2), AUTH = (1 << 3), CONN = (1 << 4), SCP = (1 << 5), SFTP = (1 << 6), ERROR = (1 << 7), internalKEY = (1 << 8), SOCKET = (1 << 9) } internal enum LIBSSH2_HOSTKEY_TYPE : Int32 { UNKNOWN = 0, RSA = 1, DSS = 2, ECDSA_256 = 3, ECDSA_384 = 4, ECDSA_521 = 5, ED25519 = 6 } internal enum SSH_DISCONNECT : Int32 { HOST_NOT_ALLOWED_TO_CONNECT = 1, PROTOCOL_ERROR = 2, KEY_EXCHANGE_FAILED = 3, RESERVED = 4, MAC_ERROR = 5, COMPRESSION_ERROR = 6, SERVICE_NOT_AVAILABLE = 7, PROTOCOL_VERSION_NOT_SUPPORTED = 8, HOST_KEY_NOT_VERIFIABLE = 9, CONNECTION_LOST = 10, BY_APPLICATION = 11, TOO_MANY_CONNECTIONS = 12, AUTH_CANCELLED_BY_USER = 13, NO_MORE_AUTH_METHODS_AVAILABLE = 14, ILLEGAL_USER_NAME = 15 } internal enum LIBSSH2_CHANNEL_EXTENDED_DATA : Int32 { NORMAL = 0, IGNORE = 1, MERGE = 2, } public enum LIBSSH2_STREAM : Int32 { NORMAL = 0, EXTENDED_DATA_STDERR = 1 } /// <summary> /// FTP File Transfer Flags. /// </summary> [Flags] internal enum LIBSSH2_FXF_FLAGS : Int32 { READ = 0x00000001, WRITE = 0x00000002, APPEND = 0x00000004, CREAT = 0x00000008, TRUNC = 0x00000010, EXCL = 0x00000020 } internal enum LIBSSH2_OPENTYPE : Int32 { OPENFILE = 0, OPENDIR = 1 } /// <summary> /// SFTP attribute flag bits /// </summary> internal enum LIBSSH2_SFTP_ATTR : uint { SIZE = 0x00000001, UIDGID = 0x00000002, PERMISSIONS = 0x00000004, ACMODTIME = 0x00000008, EXTENDED = 0x80000000, } [StructLayout(LayoutKind.Sequential)] internal struct LIBSSH2_SFTP_ATTRIBUTES { /// <summary> /// If flags & ATTR_* bit is set, then the value in this struct is /// meaningful. Otherwise it should be ignored. /// </summary> internal LIBSSH2_SFTP_ATTR flags; internal ulong filesize; internal uint uid; internal uint gid; internal FilePermissions permissions; internal uint atime; internal uint mtime; }; internal static class NativeMethods { private const string Libssh2 = "libssh2.dll"; [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern Int32 libssh2_init(Int32 flags); //--------------------------------------------------------------------- // Session functions. //--------------------------------------------------------------------- [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr Alloc( IntPtr size, IntPtr context); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr Realloc( IntPtr ptr, IntPtr size, IntPtr context); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void Free( IntPtr ptr, IntPtr context); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern Libssh2SessionHandle libssh2_session_init_ex( Alloc alloc, Free free, Realloc realloc, IntPtr context); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern Int32 libssh2_free( Libssh2SessionHandle session, IntPtr ptr); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern Int32 libssh2_session_free( IntPtr session); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern Int32 libssh2_session_disconnect_ex( Libssh2SessionHandle session, SSH_DISCONNECT reason, [MarshalAs(UnmanagedType.LPStr)] string? description, [MarshalAs(UnmanagedType.LPStr)] string? lang); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern Int32 libssh2_session_get_blocking( Libssh2SessionHandle session); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern void libssh2_session_set_blocking( Libssh2SessionHandle session, Int32 blocking); //--------------------------------------------------------------------- // Algorithm functions. //--------------------------------------------------------------------- [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr libssh2_session_methods( Libssh2SessionHandle session, LIBSSH2_METHOD methodType); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern Int32 libssh2_session_supported_algs( Libssh2SessionHandle session, LIBSSH2_METHOD methodType, [Out] out IntPtr algorithmsPtrPtr); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern Int32 libssh2_session_method_pref( Libssh2SessionHandle session, LIBSSH2_METHOD methodType, [MarshalAs(UnmanagedType.LPStr)] string prefs); //--------------------------------------------------------------------- // Banner functions. //--------------------------------------------------------------------- [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr libssh2_session_banner_get( Libssh2SessionHandle session); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern Int32 libssh2_session_banner_set( Libssh2SessionHandle session, [MarshalAs(UnmanagedType.LPStr)] string banner); //--------------------------------------------------------------------- // Handshake functions. //--------------------------------------------------------------------- // // NB. This function hangs when using libssh2 1.9.0 on Windows 10 1903. // https://github.com/libssh2/libssh2/issues/388 // [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern Int32 libssh2_session_handshake( Libssh2SessionHandle session, IntPtr socket); //--------------------------------------------------------------------- // Hostkey functions. //--------------------------------------------------------------------- [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr libssh2_session_hostkey( Libssh2SessionHandle session, out IntPtr length, out LIBSSH2_HOSTKEY_TYPE type); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr libssh2_hostkey_hash( Libssh2SessionHandle session, LIBSSH2_HOSTKEY_HASH hashType); //--------------------------------------------------------------------- // Timeout. //--------------------------------------------------------------------- [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_session_get_timeout( Libssh2SessionHandle session); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern void libssh2_session_set_timeout( Libssh2SessionHandle session, int timeout); //--------------------------------------------------------------------- // User auth. // // NB. The documentation on libssh2_userauth_publickey is extremely sparse. // For a usage example, see: // https://github.com/stuntbadger/GuacamoleServer/blob/master/src/common-ssh/ssh.c //--------------------------------------------------------------------- [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern Int32 libssh2_userauth_authenticated( Libssh2SessionHandle session); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern IntPtr libssh2_userauth_list( Libssh2SessionHandle session, [MarshalAs(UnmanagedType.LPStr)] string username, int usernameLength); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int SignCallback( IntPtr session, out IntPtr signature, out IntPtr signatureLength, IntPtr data, IntPtr dataLength, IntPtr context); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern int libssh2_userauth_publickey( Libssh2SessionHandle session, [MarshalAs(UnmanagedType.LPStr)] string username, [MarshalAs(UnmanagedType.LPArray)] byte[] publicKey, IntPtr pemPublicKeyLength, SignCallback callback, IntPtr context); [StructLayout(LayoutKind.Sequential)] internal struct LIBSSH2_USERAUTH_KBDINT_PROMPT { internal IntPtr TextPtr; internal int TextLength; internal byte Echo; } [StructLayout(LayoutKind.Sequential)] internal struct LIBSSH2_USERAUTH_KBDINT_RESPONSE { internal IntPtr TextPtr; internal int TextLength; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void KeyboardInteractiveCallback( IntPtr namePtr, int nameLength, IntPtr instructionPtr, int instructionLength, int numPrompts, IntPtr prompts, IntPtr responses, IntPtr context); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern int libssh2_userauth_keyboard_interactive_ex( Libssh2SessionHandle session, [MarshalAs(UnmanagedType.LPStr)] string username, int usernameLength, KeyboardInteractiveCallback callback, IntPtr context); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PasswordChangeCallback( IntPtr session, IntPtr newPasswordPtr, IntPtr newPasswordLengthPtr, IntPtr context); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern int libssh2_userauth_password_ex( Libssh2SessionHandle session, [MarshalAs(UnmanagedType.LPStr)] string username, int usernameLength, [MarshalAs(UnmanagedType.LPStr)] string password, int passwordLength, PasswordChangeCallback passwordChangeCallback); //--------------------------------------------------------------------- // Channel. //--------------------------------------------------------------------- [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_channel_close( Libssh2ChannelHandle channel); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_channel_free( IntPtr channel); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern Libssh2ChannelHandle libssh2_channel_open_ex( Libssh2SessionHandle session, [MarshalAs(UnmanagedType.LPStr)] string channelType, uint channelTypeLength, uint windowSize, uint packetSize, [MarshalAs(UnmanagedType.LPStr)] string? message, uint messageLength); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern int libssh2_channel_setenv_ex( Libssh2ChannelHandle channel, [MarshalAs(UnmanagedType.LPStr)] string variableName, uint variableNameLength, [MarshalAs(UnmanagedType.LPStr)] string variableValue, uint variableValueLength); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern int libssh2_channel_process_startup( Libssh2ChannelHandle channel, [MarshalAs(UnmanagedType.LPStr)] string request, uint requestLength, [MarshalAs(UnmanagedType.LPStr)] string? message, uint messageLength); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_channel_read_ex( Libssh2ChannelHandle channel, int streamId, byte[] buffer, IntPtr bufferSize); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_channel_write_ex( Libssh2ChannelHandle channel, int streamId, byte[] buffer, IntPtr bufferSize); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_channel_wait_closed( Libssh2ChannelHandle channel); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_channel_wait_eof( Libssh2ChannelHandle channel); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_channel_get_exit_status( Libssh2ChannelHandle channel); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_channel_get_exit_signal( Libssh2ChannelHandle channel, out IntPtr exitsignal, out IntPtr exitsignalLength, out IntPtr errmsg, out IntPtr errmsgLength, out IntPtr langTag, out IntPtr langTagLength); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_channel_handle_extended_data2( Libssh2ChannelHandle channel, LIBSSH2_CHANNEL_EXTENDED_DATA mode); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_channel_eof( Libssh2ChannelHandle channel); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern int libssh2_channel_request_pty_ex( Libssh2ChannelHandle channel, [MarshalAs(UnmanagedType.LPStr)] string term, uint termLength, byte[]? modes, uint modesLength, int width, int height, int widthPx, int heightPx); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_channel_request_pty_size_ex( Libssh2ChannelHandle channel, int width, int height, int widthPx, int heightPx); //--------------------------------------------------------------------- // SFTP. //--------------------------------------------------------------------- [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern Libssh2SftpChannelHandle libssh2_sftp_init( Libssh2SessionHandle session); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_sftp_shutdown( IntPtr sftp); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_sftp_last_error( Libssh2SftpChannelHandle sftp); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_sftp_close_handle( IntPtr sftp); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern Libssh2SftpFileHandle libssh2_sftp_open_ex( Libssh2SftpChannelHandle channel, [MarshalAs(UnmanagedType.LPStr)] string path, uint pathLength, LIBSSH2_FXF_FLAGS flags, FilePermissions mode, LIBSSH2_OPENTYPE openType); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_sftp_readdir_ex( Libssh2SftpFileHandle handle, IntPtr buffer, IntPtr bufferSize, IntPtr longEntry, IntPtr longEntrySize, out LIBSSH2_SFTP_ATTRIBUTES attrs); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_sftp_mkdir_ex( Libssh2SftpChannelHandle channel, [MarshalAs(UnmanagedType.LPStr)] string path, uint pathLength, FilePermissions mode); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_sftp_rmdir_ex( Libssh2SftpChannelHandle channel, [MarshalAs(UnmanagedType.LPStr)] string path, uint pathLength); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_sftp_unlink_ex( Libssh2SftpChannelHandle channel, [MarshalAs(UnmanagedType.LPStr)] string path, uint pathLength); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_sftp_read( Libssh2SftpFileHandle channel, byte[] buffer, IntPtr bufferSize); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_sftp_write( Libssh2SftpFileHandle channel, IntPtr buffer, IntPtr bufferSize); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_sftp_fstat_ex( Libssh2SftpFileHandle channel, out LIBSSH2_SFTP_ATTRIBUTES attrs, int setstat); //--------------------------------------------------------------------- // Keepalive. //--------------------------------------------------------------------- [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern void libssh2_keepalive_config( Libssh2SessionHandle session, int wantReply, uint interval); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_keepalive_send( Libssh2SessionHandle session, out int secondsToNext); //--------------------------------------------------------------------- // Error functions. //--------------------------------------------------------------------- [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_session_last_errno( Libssh2SessionHandle session); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern int libssh2_session_last_error( Libssh2SessionHandle session, out IntPtr errorMessage, out int errorMessageLength, int allocateBuffer); //--------------------------------------------------------------------- // Tracing functions. //--------------------------------------------------------------------- [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void TraceHandler( IntPtr sessionHandle, IntPtr context, IntPtr data, IntPtr length); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern void libssh2_trace( Libssh2SessionHandle session, LIBSSH2_TRACE bitmask); [DllImport(Libssh2, CallingConvention = CallingConvention.Cdecl)] internal static extern void libssh2_trace_sethandler( Libssh2SessionHandle session, IntPtr context, TraceHandler? callback); //--------------------------------------------------------------------- // Winsock. //--------------------------------------------------------------------- internal const uint WSA_WAIT_FAILED = 0xFFFFFFFF; internal const uint WSA_WAIT_EVENT_0 = 0; internal const uint WSA_WAIT_TIMEOUT = 0x102; internal const uint FD_READ = 1; internal const uint FD_WRITE = 2; internal const uint FD_OOB = 4; internal const uint FD_ACCEPT = 8; internal const uint FD_CONNECT = 16; internal const uint FD_CLOSE = 32; [DllImport("Ws2_32.dll")] internal static extern WsaEventHandle WSACreateEvent(); [DllImport("Ws2_32.dll")] internal static extern int WSAEventSelect( IntPtr socket, WsaEventHandle hande, uint eventMask); [DllImport("Ws2_32.dll")] internal static extern bool WSASetEvent(WsaEventHandle hande); [DllImport("Ws2_32.dll")] internal static extern bool WSAResetEvent(WsaEventHandle hande); [DllImport("Ws2_32.dll")] internal static extern bool WSACloseEvent(IntPtr hande); [DllImport("Ws2_32.dll")] internal static extern uint WSAWaitForMultipleEvents( uint cEvents, IntPtr[] pEvents, bool fWaitAll, uint timeout, bool fAlterable); [DllImport("Ws2_32.dll")] internal static extern int WSAEnumNetworkEvents( IntPtr socket, WsaEventHandle eventHandle, ref WSANETWORKEVENTS eventInfo); [DllImport("Ws2_32.dll")] internal static extern int WSAGetLastError(); [StructLayout(LayoutKind.Sequential)] internal struct WSANETWORKEVENTS { internal int lNetworkEvents; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] internal int[] iErrorCode; } //--------------------------------------------------------------------- // Utility functions. //--------------------------------------------------------------------- internal static string? PtrToString( IntPtr stringPtr, int stringLength, Encoding encoding) { if (stringPtr == IntPtr.Zero || stringLength == 0) { return null; } var buffer = new byte[stringLength]; Marshal.Copy(stringPtr, buffer, 0, stringLength); return encoding.GetString(buffer); } internal static T[] PtrToStructureArray<T>( IntPtr ptr, int count) where T : struct { var size = Marshal.SizeOf(typeof(T)); var array = new T[count]; for (var i = 0; i < count; i++) { var elementPtr = new IntPtr(ptr.ToInt64() + i * size); array[i] = Marshal.PtrToStructure<T>(elementPtr); } return array; } internal static void StructureArrayToPtr<T>( IntPtr ptr, T[] array) where T : struct { var size = Marshal.SizeOf(typeof(T)); for (var i = 0; i < array.Length; i++) { var elementPtr = new IntPtr(ptr.ToInt64() + i * size); Marshal.StructureToPtr(array[i], elementPtr, false); } } } //------------------------------------------------------------------------- // Safe handles. //------------------------------------------------------------------------- /// <summary> /// Safe handle for a LIBSSH2_SESSION. /// </summary> internal class Libssh2SessionHandle : SafeHandleZeroOrMinusOneIsInvalid { #if DEBUG private readonly Thread owningThread = Thread.CurrentThread; #endif private Libssh2SessionHandle() : base(true) { HandleTable.OnHandleCreated(this, "SSH session"); } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { var result = (LIBSSH2_ERROR)NativeMethods.libssh2_session_free( this.handle); Debug.Assert(result == LIBSSH2_ERROR.NONE); HandleTable.OnHandleClosed(this); return true; } [Conditional("DEBUG")] internal void CheckCurrentThreadOwnsHandle() { #if DEBUG Debug.Assert(Thread.CurrentThread == this.owningThread); #endif } } /// <summary> /// Safe handle for a resource that's dependent on a LIBSSH2_SESSION. /// </summary> internal abstract class Libssh2SessionResourceHandle : SafeHandleZeroOrMinusOneIsInvalid { #if DEBUG private readonly Thread owningThread = Thread.CurrentThread; #endif /// <summary> /// Handle to parent session. /// </summary> internal Libssh2SessionHandle? SessionHandle { get; private set; } protected Libssh2SessionResourceHandle() : base(true) { HandleTable.OnHandleCreated(this, "SSH session"); } [Conditional("DEBUG")] internal void CheckCurrentThreadOwnsHandle() { #if DEBUG Debug.Assert(Thread.CurrentThread == this.owningThread); #endif } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { // // NB. Libssh2 manages channels and SFTPs as a sub-resource of a // session. // // Freeing a sub-resource is fine if the session is still around, // but trying to free a sub-resource *after* freeing a session will // cause an access violation. // // When calling dispose, it's therefore important to dispose // sub-resource before disposing their parent session - that's // what the following assertion is for. // // When handles are not disposed (for example, because the app // is being force-closed), then the finalizer may release // resources in arbitrary order. // Invariant.ExpectNotNull(this.SessionHandle, nameof(this.SessionHandle)); Debug.Assert(!this.SessionHandle!.IsClosed); CheckCurrentThreadOwnsHandle(); if (this.SessionHandle.IsClosed) { // Do not free the channel. return false; } // // Release the actual handle. // ProtectedReleaseHandle(); HandleTable.OnHandleClosed(this); return true; } internal void ValidateAndAttachToSession(Libssh2Session session) { Precondition.ExpectNotNull(session, nameof(session)); if (this.IsInvalid) { throw session.CreateException( (LIBSSH2_ERROR)NativeMethods.libssh2_session_last_errno( session.Handle)); } this.SessionHandle = session.Handle; } protected abstract void ProtectedReleaseHandle(); } /// <summary> /// Safe handle for LIBSSH2_CHANNEL. /// </summary> internal class Libssh2ChannelHandle : Libssh2SessionResourceHandle { protected override void ProtectedReleaseHandle() { LIBSSH2_ERROR result; while ((result = (LIBSSH2_ERROR) NativeMethods.libssh2_channel_free(this.handle)) == LIBSSH2_ERROR.EAGAIN) { }; Debug.Assert(result == LIBSSH2_ERROR.NONE); } } /// <summary> /// Safe handle for LIBSSH2_SFTP. /// </summary> internal class Libssh2SftpChannelHandle : Libssh2SessionResourceHandle { protected override void ProtectedReleaseHandle() { LIBSSH2_ERROR result; while ((result = (LIBSSH2_ERROR) NativeMethods.libssh2_sftp_shutdown(this.handle)) == LIBSSH2_ERROR.EAGAIN) { }; Debug.Assert(result == LIBSSH2_ERROR.NONE); } } /// <summary> /// Safe handle for LIBSSH2_SFTP_HANDLE. /// </summary> internal class Libssh2SftpFileHandle : Libssh2SessionResourceHandle { protected override void ProtectedReleaseHandle() { LIBSSH2_ERROR result; while ((result = (LIBSSH2_ERROR) NativeMethods.libssh2_sftp_close_handle(this.handle)) == LIBSSH2_ERROR.EAGAIN) { }; Debug.Assert(result == LIBSSH2_ERROR.NONE); } } internal class WsaEventHandle : SafeHandleZeroOrMinusOneIsInvalid { private WsaEventHandle() : base(true) { } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { var result = NativeMethods.WSACloseEvent(this.handle); Debug.Assert(result); return result; } } }