ソースを参照

Client: Preliminary SMB2 client implementation

Tal Aloni 7 年 前
コミット
4043bfb5f2
2 ファイル変更357 行追加0 行削除
  1. 356 0
      SMBLibrary/Client/SMB2Client.cs
  2. 1 0
      SMBLibrary/SMBLibrary.csproj

+ 356 - 0
SMBLibrary/Client/SMB2Client.cs

@@ -0,0 +1,356 @@
+/* Copyright (C) 2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+ * 
+ * You can redistribute this program and/or modify it under the terms of
+ * the GNU Lesser Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ */
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using SMBLibrary.Authentication.NTLM;
+using SMBLibrary.NetBios;
+using SMBLibrary.Services;
+using SMBLibrary.SMB2;
+using Utilities;
+
+namespace SMBLibrary.Client
+{
+    public class SMB2Client
+    {
+        public const int NetBiosOverTCPPort = 139;
+        public const int DirectTCPPort = 445;
+
+        private SMBTransportType m_transport;
+        private bool m_isConnected;
+        private bool m_isLoggedIn;
+        private Socket m_clientSocket;
+        private IAsyncResult m_currentAsyncResult;
+
+        private object m_incomingQueueLock = new object();
+        private List<SMB2Command> m_incomingQueue = new List<SMB2Command>();
+        private EventWaitHandle m_incomingQueueEventHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
+
+        private uint m_messageID = 0;
+        private SMB2Dialect m_dialect;
+        private uint m_maxTransactSize;
+        private uint m_maxReadSize;
+        private uint m_maxWriteSize;
+        private ulong m_sessionID;
+        private byte[] m_securityBlob;
+        private byte[] m_sessionKey;
+
+        public SMB2Client()
+        {
+        }
+
+        public bool Connect(IPAddress serverAddress, SMBTransportType transport)
+        {
+            m_transport = transport;
+            if (!m_isConnected)
+            {
+                m_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+                int port;
+                if (transport == SMBTransportType.DirectTCPTransport)
+                {
+                    port = DirectTCPPort;
+                }
+                else
+                {
+                    port = NetBiosOverTCPPort;
+                }
+
+                try
+                {
+                    m_clientSocket.Connect(serverAddress, port);
+                }
+                catch (SocketException)
+                {
+                    return false;
+                }
+
+                ConnectionState state = new ConnectionState();
+                NBTConnectionReceiveBuffer buffer = state.ReceiveBuffer;
+                m_currentAsyncResult = m_clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnClientSocketReceive), state);
+                bool supportsSMB2 = NegotiateDialect();
+                if (!supportsSMB2)
+                {
+                    m_clientSocket.Close();
+                }
+                else
+                {
+                    m_isConnected = true;
+                }
+            }
+            return m_isConnected;
+        }
+
+        public void Disconnect()
+        {
+            if (m_isConnected)
+            {
+                m_clientSocket.Disconnect(false);
+                m_isConnected = false;
+            }
+        }
+
+        private bool NegotiateDialect()
+        {
+            NegotiateRequest request = new NegotiateRequest();
+            request.SecurityMode = SecurityMode.SigningEnabled;
+            request.ClientGuid = Guid.NewGuid();
+            request.ClientStartTime = DateTime.Now;
+            request.Dialects.Add(SMB2Dialect.SMB202);
+            request.Dialects.Add(SMB2Dialect.SMB210);
+
+            TrySendCommand(request);
+            NegotiateResponse response = WaitForCommand(SMB2CommandName.Negotiate) as NegotiateResponse;
+            if (response != null && response.Header.Status == NTStatus.STATUS_SUCCESS)
+            {
+                m_dialect = response.DialectRevision;
+                m_maxTransactSize = response.MaxTransactSize;
+                m_maxReadSize = response.MaxReadSize;
+                m_maxWriteSize = response.MaxWriteSize;
+                m_securityBlob = response.SecurityBuffer;
+                return true;
+            }
+            return false;
+        }
+
+        public NTStatus Login(string domainName, string userName, string password)
+        {
+            return Login(domainName, userName, password, AuthenticationMethod.NTLMv2);
+        }
+
+        public NTStatus Login(string domainName, string userName, string password, AuthenticationMethod authenticationMethod)
+        {
+            if (!m_isConnected)
+            {
+                throw new InvalidOperationException("A connection must be successfully established before attempting login");
+            }
+
+            SessionSetupRequest request = new SessionSetupRequest();
+            request.SecurityMode = SecurityMode.SigningEnabled;
+            request.SecurityBuffer = NTLMAuthenticationHelper.GetNegotiateMessage(m_securityBlob, domainName, authenticationMethod);
+            TrySendCommand(request);
+            SMB2Command response = WaitForCommand(SMB2CommandName.SessionSetup);
+            if (response != null)
+            {
+                if (response.Header.Status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED && response is SessionSetupResponse)
+                {
+                    m_sessionID = response.Header.SessionID;
+                    request = new SessionSetupRequest();
+                    request.SecurityMode = SecurityMode.SigningEnabled;
+                    request.SecurityBuffer = NTLMAuthenticationHelper.GetAuthenticateMessage(((SessionSetupResponse)response).SecurityBuffer, domainName, userName, password, authenticationMethod, out m_sessionKey);
+                    TrySendCommand(request);
+                    response = WaitForCommand(SMB2CommandName.SessionSetup);
+                    if (response != null)
+                    {
+                        m_isLoggedIn = (response.Header.Status == NTStatus.STATUS_SUCCESS);
+                        return response.Header.Status;
+                    }
+                }
+                else
+                {
+                    return response.Header.Status;
+                }
+            }
+            return NTStatus.STATUS_INVALID_SMB;
+        }
+
+        public NTStatus Logoff()
+        {
+            LogoffRequest request = new LogoffRequest();
+            TrySendCommand(request);
+
+            SMB2Command response = WaitForCommand(SMB2CommandName.Logoff);
+            if (response != null)
+            {
+                m_isLoggedIn = (response.Header.Status != NTStatus.STATUS_SUCCESS);
+                return response.Header.Status;
+            }
+            return NTStatus.STATUS_INVALID_SMB;
+        }
+
+        private void OnClientSocketReceive(IAsyncResult ar)
+        {
+            if (ar != m_currentAsyncResult)
+            {
+                // We ignore calls for old sockets which we no longer use
+                // See: http://rajputyh.blogspot.co.il/2010/04/solve-exception-message-iasyncresult.html
+                return;
+            }
+
+            ConnectionState state = (ConnectionState)ar.AsyncState;
+
+            if (!m_clientSocket.Connected)
+            {
+                return;
+            }
+
+            int numberOfBytesReceived = 0;
+            try
+            {
+                numberOfBytesReceived = m_clientSocket.EndReceive(ar);
+            }
+            catch (ObjectDisposedException)
+            {
+                Log("[ReceiveCallback] EndReceive ObjectDisposedException");
+                return;
+            }
+            catch (SocketException ex)
+            {
+                Log("[ReceiveCallback] EndReceive SocketException: " + ex.Message);
+                return;
+            }
+
+            if (numberOfBytesReceived == 0)
+            {
+                m_isConnected = false;
+            }
+            else
+            {
+                NBTConnectionReceiveBuffer buffer = state.ReceiveBuffer;
+                buffer.SetNumberOfBytesReceived(numberOfBytesReceived);
+                ProcessConnectionBuffer(state);
+
+                try
+                {
+                    m_currentAsyncResult = m_clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnClientSocketReceive), state);
+                }
+                catch (ObjectDisposedException)
+                {
+                    m_isConnected = false;
+                    Log("[ReceiveCallback] BeginReceive ObjectDisposedException");
+                }
+                catch (SocketException ex)
+                {
+                    m_isConnected = false;
+                    Log("[ReceiveCallback] BeginReceive SocketException: " + ex.Message);
+                }
+            }
+        }
+
+        private void ProcessConnectionBuffer(ConnectionState state)
+        {
+            NBTConnectionReceiveBuffer receiveBuffer = state.ReceiveBuffer;
+            while (receiveBuffer.HasCompletePacket())
+            {
+                SessionPacket packet = null;
+                try
+                {
+                    packet = receiveBuffer.DequeuePacket();
+                }
+                catch (Exception)
+                {
+                    m_clientSocket.Close();
+                    break;
+                }
+
+                if (packet != null)
+                {
+                    ProcessPacket(packet, state);
+                }
+            }
+        }
+
+        private void ProcessPacket(SessionPacket packet, ConnectionState state)
+        {
+            if (packet is SessionKeepAlivePacket && m_transport == SMBTransportType.NetBiosOverTCP)
+            {
+                // [RFC 1001] NetBIOS session keep alives do not require a response from the NetBIOS peer
+            }
+            else if (packet is PositiveSessionResponsePacket && m_transport == SMBTransportType.NetBiosOverTCP)
+            {
+            }
+            else if (packet is NegativeSessionResponsePacket && m_transport == SMBTransportType.NetBiosOverTCP)
+            {
+                m_clientSocket.Close();
+                m_isConnected = false;
+            }
+            else if (packet is SessionMessagePacket)
+            {
+                SMB2Command command;
+                try
+                {
+                    command = SMB2Command.ReadResponse(packet.Trailer, 0);
+                }
+                catch (Exception ex)
+                {
+                    Log("Invalid SMB2 response: " + ex.Message);
+                    m_clientSocket.Close();
+                    m_isConnected = false;
+                    return;
+                }
+
+                lock (m_incomingQueueLock)
+                {
+                    m_incomingQueue.Add(command);
+                    m_incomingQueueEventHandle.Set();
+                }
+            }
+        }
+
+        internal SMB2Command WaitForCommand(SMB2CommandName commandName)
+        {
+            const int TimeOut = 5000;
+            Stopwatch stopwatch = new Stopwatch();
+            stopwatch.Start();
+            while (stopwatch.ElapsedMilliseconds < TimeOut)
+            {
+                lock (m_incomingQueueLock)
+                {
+                    for (int index = 0; index < m_incomingQueue.Count; index++)
+                    {
+                        SMB2Command command = m_incomingQueue[index];
+
+                        if (command.CommandName == commandName)
+                        {
+                            m_incomingQueue.RemoveAt(index);
+                            return command;
+                        }
+                    }
+                }
+                m_incomingQueueEventHandle.WaitOne(100);
+            }
+            return null;
+        }
+
+        private void Log(string message)
+        {
+            System.Diagnostics.Debug.Print(message);
+        }
+
+        internal void TrySendCommand(SMB2Command request)
+        {
+            request.Header.Credits = 1;
+            request.Header.MessageID = m_messageID;
+            request.Header.SessionID = m_sessionID;
+            TrySendCommand(m_clientSocket, request);
+            m_messageID++;
+        }
+
+        public static void TrySendCommand(Socket socket, SMB2Command request)
+        {
+            SessionMessagePacket packet = new SessionMessagePacket();
+            packet.Trailer = request.GetBytes();
+            TrySendPacket(socket, packet);
+        }
+
+        public static void TrySendPacket(Socket socket, SessionPacket packet)
+        {
+            try
+            {
+                socket.Send(packet.GetBytes());
+            }
+            catch (SocketException)
+            {
+            }
+            catch (ObjectDisposedException)
+            {
+            }
+        }
+    }
+}

+ 1 - 0
SMBLibrary/SMBLibrary.csproj

@@ -60,6 +60,7 @@
     <Compile Include="Client\Helpers\ServerServiceHelper.cs" />
     <Compile Include="Client\SMB1Client.cs" />
     <Compile Include="Client\SMB1FileStore.cs" />
+    <Compile Include="Client\SMB2Client.cs" />
     <Compile Include="Enums\NTStatus.cs" />
     <Compile Include="Enums\SMBTransportType.cs" />
     <Compile Include="Enums\Win32Error.cs" />