diff --git a/SMBLibrary/Enums/NTStatus.cs b/SMBLibrary/Enums/NTStatus.cs
index 9c956b5..d041cbc 100644
--- a/SMBLibrary/Enums/NTStatus.cs
+++ b/SMBLibrary/Enums/NTStatus.cs
@@ -4,7 +4,10 @@ namespace SMBLibrary
public enum NTStatus : uint
{
STATUS_SUCCESS = 0x00000000,
+ STATUS_OBJECT_NAME_EXISTS = 0x40000000,
+ STATUS_NO_MORE_FILES = 0x80000006,
STATUS_NOT_IMPLEMENTED = 0xC0000002,
+ STATUS_INVALID_INFO_CLASS = 0xC0000003,
STATUS_INVALID_HANDLE = 0xC0000008,
STATUS_INVALID_PARAMETER = 0xC000000D,
STATUS_NO_SUCH_DEVICE = 0xC000000E,
@@ -23,12 +26,16 @@ namespace SMBLibrary
STATUS_LOGON_FAILURE = 0xC000006D, // Authentication failure.
STATUS_ACCOUNT_RESTRICTION = 0xC000006E, // The user has an empty password, which is not allowed
STATUS_DISK_FULL = 0xC000007F,
+ STATUS_INSUFFICIENT_RESOURCES = 0xC000009A,
STATUS_MEDIA_WRITE_PROTECTED = 0xC00000A2,
STATUS_FILE_IS_A_DIRECTORY = 0xC00000BA,
STATUS_NOT_SUPPORTED = 0xC00000BB,
+ STATUS_NETWORK_NAME_DELETED = 0xC00000C9,
STATUS_TOO_MANY_SESSIONS = 0xC00000CE,
STATUS_TOO_MANY_OPENED_FILES = 0xC000011F,
STATUS_CANNOT_DELETE = 0xC0000121,
+ STATUS_FILE_CLOSED = 0xC0000128,
+ STATUS_FS_DRIVER_REQUIRED = 0xC000019C,
STATUS_USER_SESSION_DELETED = 0xC0000203,
STATUS_INSUFF_SERVER_RESOURCES = 0xC0000205,
diff --git a/SMBLibrary/Enums/Win32Error.cs b/SMBLibrary/Enums/Win32Error.cs
index 99fbb4e..7daa5ce 100644
--- a/SMBLibrary/Enums/Win32Error.cs
+++ b/SMBLibrary/Enums/Win32Error.cs
@@ -8,6 +8,7 @@ namespace SMBLibrary
ERROR_ACCESS_DENIED = 0x0005,
ERROR_SHARING_VIOLATION = 0x0020,
ERROR_DISK_FULL = 0x0070,
+ ERROR_ALREADY_EXISTS = 0x00B7,
ERROR_LOGON_FAILURE = 0x052E,
ERROR_ACCOUNT_RESTRICTION = 0x052F,
ERROR_ACCOUNT_DISABLED = 0x0533,
diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj
index abf35a0..c9413a2 100644
--- a/SMBLibrary/SMBLibrary.csproj
+++ b/SMBLibrary/SMBLibrary.csproj
@@ -118,12 +118,17 @@
+
+
+
+
+
@@ -148,8 +153,18 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/SMBLibrary/Server/ConnectionState/ConnectionState.cs b/SMBLibrary/Server/ConnectionState/ConnectionState.cs
index 1aa4be9..c4b2068 100644
--- a/SMBLibrary/Server/ConnectionState/ConnectionState.cs
+++ b/SMBLibrary/Server/ConnectionState/ConnectionState.cs
@@ -19,6 +19,8 @@ namespace SMBLibrary.Server
{
NotSet,
NTLM012, // NT LM 0.12
+ SMB202, // SMB 2.0.2
+ SMB210, // SMB 2.1
}
public class ConnectionState
diff --git a/SMBLibrary/Server/ConnectionState/SMB2ConnectionState.cs b/SMBLibrary/Server/ConnectionState/SMB2ConnectionState.cs
new file mode 100644
index 0000000..69901bd
--- /dev/null
+++ b/SMBLibrary/Server/ConnectionState/SMB2ConnectionState.cs
@@ -0,0 +1,71 @@
+/* Copyright (C) 2014-2017 Tal Aloni . 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.IO;
+using SMBLibrary.SMB2;
+using Utilities;
+
+namespace SMBLibrary.Server
+{
+ public delegate ulong? AllocatePersistentFileID();
+
+ public class SMB2ConnectionState : ConnectionState
+ {
+ // Key is SessionID
+ private Dictionary m_sessions = new Dictionary();
+ private ulong m_nextSessionID = 1;
+ public AllocatePersistentFileID AllocatePersistentFileID;
+
+ public SMB2ConnectionState(ConnectionState state, AllocatePersistentFileID allocatePersistentFileID) : base(state)
+ {
+ AllocatePersistentFileID = allocatePersistentFileID;
+ }
+
+ public ulong? AllocateSessionID()
+ {
+ for (ulong offset = 0; offset < UInt64.MaxValue; offset++)
+ {
+ ulong sessionID = (ulong)(m_nextSessionID + offset);
+ if (sessionID == 0 || sessionID == 0xFFFFFFFF)
+ {
+ continue;
+ }
+ if (!m_sessions.ContainsKey(sessionID))
+ {
+ m_nextSessionID = (ulong)(sessionID + 1);
+ return sessionID;
+ }
+ }
+ return null;
+ }
+
+ public SMB2Session CreateSession(ulong sessionID, string userName)
+ {
+ SMB2Session session = new SMB2Session(this, sessionID, userName);
+ m_sessions.Add(sessionID, session);
+ return session;
+ }
+
+ public SMB2Session GetSession(ulong sessionID)
+ {
+ SMB2Session session;
+ m_sessions.TryGetValue(sessionID, out session);
+ return session;
+ }
+
+ public void RemoveSession(ulong sessionID)
+ {
+ m_sessions.Remove(sessionID);
+ }
+
+ public void ClearSessions()
+ {
+ m_sessions.Clear();
+ }
+ }
+}
diff --git a/SMBLibrary/Server/ConnectionState/SMB2Session.cs b/SMBLibrary/Server/ConnectionState/SMB2Session.cs
new file mode 100644
index 0000000..7a6d1f8
--- /dev/null
+++ b/SMBLibrary/Server/ConnectionState/SMB2Session.cs
@@ -0,0 +1,165 @@
+/* Copyright (C) 2014-2017 Tal Aloni . 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.IO;
+using SMBLibrary.SMB2;
+using Utilities;
+
+namespace SMBLibrary.Server
+{
+ public class SMB2Session
+ {
+ private SMB2ConnectionState m_connection;
+ private ulong m_sessionID;
+ private string m_userName;
+
+ // Key is TreeID
+ private Dictionary m_connectedTrees = new Dictionary();
+ private uint m_nextTreeID = 1; // TreeID uniquely identifies a tree connect within the scope of the session
+
+ // Key is the persistent portion of the FileID
+ private Dictionary m_openFiles = new Dictionary();
+
+ // Key is the persistent portion of the FileID
+ private Dictionary m_openSearches = new Dictionary();
+
+ public SMB2Session(SMB2ConnectionState connecton, ulong sessionID, string userName)
+ {
+ m_connection = connecton;
+ m_sessionID = sessionID;
+ m_userName = userName;
+ }
+
+ private uint? AllocateTreeID()
+ {
+ for (uint offset = 0; offset < UInt32.MaxValue; offset++)
+ {
+ uint treeID = (uint)(m_nextTreeID + offset);
+ if (treeID == 0 || treeID == 0xFFFFFFFF)
+ {
+ continue;
+ }
+ if (!m_connectedTrees.ContainsKey(treeID))
+ {
+ m_nextTreeID = (uint)(treeID + 1);
+ return treeID;
+ }
+ }
+ return null;
+ }
+
+ public uint? AddConnectedTree(ISMBShare share)
+ {
+ uint? treeID = AllocateTreeID();
+ if (treeID.HasValue)
+ {
+ m_connectedTrees.Add(treeID.Value, share);
+ }
+ return treeID;
+ }
+
+ public ISMBShare GetConnectedTree(uint treeID)
+ {
+ if (m_connectedTrees.ContainsKey(treeID))
+ {
+ return m_connectedTrees[treeID];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public void RemoveConnectedTree(uint treeID)
+ {
+ m_connectedTrees.Remove(treeID);
+ }
+
+ public void RemoveConnectedTrees()
+ {
+ m_connectedTrees.Clear();
+ }
+
+ public bool IsTreeConnected(uint treeID)
+ {
+ return m_connectedTrees.ContainsKey(treeID);
+ }
+
+ /// Should include the path relative to the share
+ /// The persistent portion of the FileID
+ public ulong? AddOpenFile(string relativePath)
+ {
+ return AddOpenFile(relativePath, null);
+ }
+
+ public ulong? AddOpenFile(string relativePath, Stream stream)
+ {
+ return AddOpenFile(relativePath, stream, false);
+ }
+
+ public ulong? AddOpenFile(string relativePath, Stream stream, bool deleteOnClose)
+ {
+ ulong? persistentID = m_connection.AllocatePersistentFileID();
+ if (persistentID.HasValue)
+ {
+ m_openFiles.Add(persistentID.Value, new OpenFileObject(relativePath, stream, deleteOnClose));
+ }
+ return persistentID;
+ }
+
+ public OpenFileObject GetOpenFileObject(ulong fileID)
+ {
+ if (m_openFiles.ContainsKey(fileID))
+ {
+ return m_openFiles[fileID];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public void RemoveOpenFile(ulong fileID)
+ {
+ Stream stream = m_openFiles[fileID].Stream;
+ if (stream != null)
+ {
+ stream.Close();
+ }
+ m_openFiles.Remove(fileID);
+ m_openSearches.Remove(fileID);
+ }
+
+ public OpenSearch AddOpenSearch(ulong fileID, List entries, int enumerationLocation)
+ {
+ OpenSearch openSearch = new OpenSearch(entries, enumerationLocation);
+ m_openSearches.Add(fileID, openSearch);
+ return openSearch;
+ }
+
+ public OpenSearch GetOpenSearch(ulong fileID)
+ {
+ OpenSearch openSearch;
+ m_openSearches.TryGetValue(fileID, out openSearch);
+ return openSearch;
+ }
+
+ public void RemoveOpenSearch(ulong fileID)
+ {
+ m_openSearches.Remove(fileID);
+ }
+
+ public string UserName
+ {
+ get
+ {
+ return m_userName;
+ }
+ }
+ }
+}
diff --git a/SMBLibrary/Server/Helpers/NTFileSystemHelper.Query.cs b/SMBLibrary/Server/Helpers/NTFileSystemHelper.Query.cs
new file mode 100644
index 0000000..19a045a
--- /dev/null
+++ b/SMBLibrary/Server/Helpers/NTFileSystemHelper.Query.cs
@@ -0,0 +1,191 @@
+/* Copyright (C) 2014-2017 Tal Aloni . 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.IO;
+using Utilities;
+
+namespace SMBLibrary.Server
+{
+ public partial class NTFileSystemHelper
+ {
+ public static NTStatus GetNamedPipeInformation(out FileInformation result, FileInformationClass informationClass)
+ {
+ switch (informationClass)
+ {
+ case FileInformationClass.FileBasicInformation:
+ {
+ FileBasicInformation information = new FileBasicInformation();
+ information.FileAttributes = FileAttributes.Temporary;
+ result = information;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ case FileInformationClass.FileStandardInformation:
+ {
+ FileStandardInformation information = new FileStandardInformation();
+ information.DeletePending = true;
+ result = information;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ default:
+ result = null;
+ return NTStatus.STATUS_INVALID_INFO_CLASS;
+ }
+ }
+
+ public static NTStatus GetFileInformation(out FileInformation result, FileSystemEntry entry, bool deletePending, FileInformationClass informationClass)
+ {
+ switch (informationClass)
+ {
+ case FileInformationClass.FileBasicInformation:
+ {
+ FileBasicInformation information = new FileBasicInformation();
+ information.CreationTime = entry.CreationTime;
+ information.LastAccessTime = entry.LastAccessTime;
+ information.LastWriteTime = entry.LastWriteTime;
+ information.ChangeTime = entry.LastWriteTime;
+ information.FileAttributes = GetFileAttributes(entry);
+ result = information;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ case FileInformationClass.FileStandardInformation:
+ {
+ FileStandardInformation information = new FileStandardInformation();
+ information.AllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size);
+ information.EndOfFile = entry.Size;
+ information.Directory = entry.IsDirectory;
+ information.DeletePending = deletePending;
+ result = information;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ case FileInformationClass.FileInternalInformation:
+ {
+ FileInternalInformation information = new FileInternalInformation();
+ result = information;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ case FileInformationClass.FileEaInformation:
+ {
+ FileEaInformation information = new FileEaInformation();
+ information.EaSize = 0;
+ result = information;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ case FileInformationClass.FilePositionInformation:
+ {
+ result = null;
+ return NTStatus.STATUS_NOT_IMPLEMENTED;
+ }
+ case FileInformationClass.FileFullEaInformation:
+ {
+ result = null;
+ return NTStatus.STATUS_NOT_IMPLEMENTED;
+ }
+ case FileInformationClass.FileModeInformation:
+ {
+ result = null;
+ return NTStatus.STATUS_NOT_IMPLEMENTED;
+ }
+ case FileInformationClass.FileAlignmentInformation:
+ {
+ result = null;
+ return NTStatus.STATUS_NOT_IMPLEMENTED;
+ }
+ case FileInformationClass.FileAllInformation:
+ {
+ result = null;
+ return NTStatus.STATUS_NOT_IMPLEMENTED;
+ }
+ case FileInformationClass.FileAlternateNameInformation:
+ {
+ result = null;
+ return NTStatus.STATUS_NOT_IMPLEMENTED;
+ }
+ case FileInformationClass.FileStreamInformation:
+ {
+ // This information class is used to enumerate the data streams of a file or a directory.
+ // A buffer of FileStreamInformation data elements is returned by the server.
+ FileStreamInformation information = new FileStreamInformation();
+ information.StreamSize = entry.Size;
+ information.StreamAllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size);
+ information.StreamName = "::$DATA";
+ result = information;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ case FileInformationClass.FilePipeInformation:
+ {
+ result = null;
+ return NTStatus.STATUS_NOT_IMPLEMENTED;
+ }
+ case FileInformationClass.FilePipeLocalInformation:
+ {
+ result = null;
+ return NTStatus.STATUS_NOT_IMPLEMENTED;
+ }
+ case FileInformationClass.FilePipeRemoteInformation:
+ {
+ result = null;
+ return NTStatus.STATUS_NOT_IMPLEMENTED;
+ }
+ case FileInformationClass.FileCompressionInformation:
+ {
+ result = null;
+ return NTStatus.STATUS_NOT_IMPLEMENTED;
+ }
+ case FileInformationClass.FileNetworkOpenInformation:
+ {
+ FileNetworkOpenInformation information = new FileNetworkOpenInformation();
+ information.CreationTime = entry.CreationTime;
+ information.LastAccessTime = entry.LastAccessTime;
+ information.LastWriteTime = entry.LastWriteTime;
+ information.ChangeTime = entry.LastWriteTime;
+ information.AllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size);
+ information.EndOfFile = entry.Size;
+ information.FileAttributes = GetFileAttributes(entry);
+ result = information;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ case FileInformationClass.FileAttributeTagInformation:
+ {
+ result = null;
+ return NTStatus.STATUS_NOT_IMPLEMENTED;
+ }
+ default:
+ result = null;
+ return NTStatus.STATUS_INVALID_INFO_CLASS;
+ }
+ }
+
+ public static FileAttributes GetFileAttributes(FileSystemEntry entry)
+ {
+ FileAttributes attributes = 0;
+ if (entry.IsHidden)
+ {
+ attributes |= FileAttributes.Hidden;
+ }
+ if (entry.IsReadonly)
+ {
+ attributes |= FileAttributes.ReadOnly;
+ }
+ if (entry.IsArchived)
+ {
+ attributes |= FileAttributes.Archive;
+ }
+ if (entry.IsDirectory)
+ {
+ attributes |= FileAttributes.Directory;
+ }
+
+ if (attributes == 0)
+ {
+ attributes = FileAttributes.Normal;
+ }
+
+ return attributes;
+ }
+ }
+}
diff --git a/SMBLibrary/Server/Helpers/NTFileSystemHelper.QueryFileSystem.cs b/SMBLibrary/Server/Helpers/NTFileSystemHelper.QueryFileSystem.cs
new file mode 100644
index 0000000..b4ca2c2
--- /dev/null
+++ b/SMBLibrary/Server/Helpers/NTFileSystemHelper.QueryFileSystem.cs
@@ -0,0 +1,101 @@
+/* Copyright (C) 2014-2017 Tal Aloni . 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.IO;
+using Utilities;
+
+namespace SMBLibrary.Server
+{
+ public partial class NTFileSystemHelper
+ {
+ public static NTStatus GetFileSystemInformation(out FileSystemInformation result, FileSystemInformationClass informationClass, IFileSystem fileSystem)
+ {
+ switch (informationClass)
+ {
+ case FileSystemInformationClass.FileFsVolumeInformation:
+ {
+ FileFsVolumeInformation information = new FileFsVolumeInformation();
+ information.SupportsObjects = false;
+ result = information;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ case FileSystemInformationClass.FileFsSizeInformation:
+ {
+ FileFsSizeInformation information = new FileFsSizeInformation();
+ information.TotalAllocationUnits = fileSystem.Size / NTFileSystemHelper.ClusterSize;
+ information.AvailableAllocationUnits = fileSystem.FreeSpace / NTFileSystemHelper.ClusterSize;
+ information.SectorsPerAllocationUnit = NTFileSystemHelper.ClusterSize / NTFileSystemHelper.BytesPerSector;
+ information.BytesPerSector = NTFileSystemHelper.BytesPerSector;
+ result = information;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ case FileSystemInformationClass.FileFsDeviceInformation:
+ {
+ FileFsDeviceInformation information = new FileFsDeviceInformation();
+ information.DeviceType = DeviceType.Disk;
+ information.Characteristics = DeviceCharacteristics.IsMounted;
+ result = information;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ case FileSystemInformationClass.FileFsAttributeInformation:
+ {
+ FileFsAttributeInformation information = new FileFsAttributeInformation();
+ information.FileSystemAttributes = FileSystemAttributes.UnicodeOnDisk;
+ information.MaximumComponentNameLength = 255;
+ information.FileSystemName = fileSystem.Name;
+ result = information;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ case FileSystemInformationClass.FileFsControlInformation:
+ {
+ FileFsControlInformation information = new FileFsControlInformation();
+ information.FileSystemControlFlags = FileSystemControlFlags.ContentIndexingDisabled;
+ information.DefaultQuotaThreshold = UInt64.MaxValue;
+ information.DefaultQuotaLimit = UInt64.MaxValue;
+ result = information;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ case FileSystemInformationClass.FileFsFullSizeInformation:
+ {
+ FileFsFullSizeInformation information = new FileFsFullSizeInformation();
+ information.TotalAllocationUnits = fileSystem.Size / NTFileSystemHelper.ClusterSize;
+ information.CallerAvailableAllocationUnits = fileSystem.FreeSpace / NTFileSystemHelper.ClusterSize;
+ information.ActualAvailableAllocationUnits = fileSystem.FreeSpace / NTFileSystemHelper.ClusterSize;
+ information.SectorsPerAllocationUnit = NTFileSystemHelper.ClusterSize / NTFileSystemHelper.BytesPerSector;
+ information.BytesPerSector = NTFileSystemHelper.BytesPerSector;
+ result = information;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ case FileSystemInformationClass.FileFsObjectIdInformation:
+ {
+ result = null;
+ // STATUS_INVALID_PARAMETER is returned when the file system does not implement object IDs
+ // See: https://msdn.microsoft.com/en-us/library/cc232106.aspx
+ return NTStatus.STATUS_INVALID_PARAMETER;
+ }
+ case FileSystemInformationClass.FileFsSectorSizeInformation:
+ {
+ FileFsSectorSizeInformation information = new FileFsSectorSizeInformation();
+ information.LogicalBytesPerSector = NTFileSystemHelper.BytesPerSector;
+ information.PhysicalBytesPerSectorForAtomicity = NTFileSystemHelper.BytesPerSector;
+ information.PhysicalBytesPerSectorForPerformance = NTFileSystemHelper.BytesPerSector;
+ information.FileSystemEffectivePhysicalBytesPerSectorForAtomicity = NTFileSystemHelper.BytesPerSector;
+ information.ByteOffsetForSectorAlignment = 0;
+ information.ByteOffsetForPartitionAlignment = 0;
+ result = information;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ default:
+ {
+ result = null;
+ return NTStatus.STATUS_INVALID_INFO_CLASS;
+ }
+ }
+ }
+ }
+}
diff --git a/SMBLibrary/Server/Helpers/NTFileSystemHelper.Set.cs b/SMBLibrary/Server/Helpers/NTFileSystemHelper.Set.cs
new file mode 100644
index 0000000..498b8ab
--- /dev/null
+++ b/SMBLibrary/Server/Helpers/NTFileSystemHelper.Set.cs
@@ -0,0 +1,207 @@
+/* Copyright (C) 2014-2017 Tal Aloni . 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.IO;
+using Utilities;
+
+namespace SMBLibrary.Server
+{
+ public partial class NTFileSystemHelper
+ {
+ public static NTStatus SetFileInformation(IFileSystem fileSystem, OpenFileObject openFile, FileInformation information, ConnectionState state)
+ {
+ if (information is FileBasicInformation)
+ {
+ FileBasicInformation basicInformation = (FileBasicInformation)information;
+ bool isHidden = ((basicInformation.FileAttributes & FileAttributes.Hidden) > 0);
+ bool isReadonly = (basicInformation.FileAttributes & FileAttributes.ReadOnly) > 0;
+ bool isArchived = (basicInformation.FileAttributes & FileAttributes.Archive) > 0;
+ try
+ {
+ fileSystem.SetAttributes(openFile.Path, isHidden, isReadonly, isArchived);
+ }
+ catch (UnauthorizedAccessException)
+ {
+ state.LogToServer(Severity.Debug, "SetFileInformation: Failed to set file attributes on '{0}'. Access Denied.", openFile.Path);
+ return NTStatus.STATUS_ACCESS_DENIED;
+ }
+
+ try
+ {
+ fileSystem.SetDates(openFile.Path, basicInformation.CreationTime, basicInformation.LastWriteTime, basicInformation.LastAccessTime);
+ }
+ catch (IOException ex)
+ {
+ ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex);
+ if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION)
+ {
+ state.LogToServer(Severity.Debug, "SetFileInformation: Failed to set file dates on '{0}'. Sharing Violation.", openFile.Path);
+ return NTStatus.STATUS_SHARING_VIOLATION;
+ }
+ else
+ {
+ state.LogToServer(Severity.Debug, "SetFileInformation: Failed to set file dates on '{0}'. Data Error.", openFile.Path);
+ return NTStatus.STATUS_DATA_ERROR;
+ }
+ }
+ catch (UnauthorizedAccessException)
+ {
+ state.LogToServer(Severity.Debug, "SetFileInformation: Failed to set file dates on '{0}'. Access Denied.", openFile.Path);
+ return NTStatus.STATUS_ACCESS_DENIED;
+ }
+ return NTStatus.STATUS_SUCCESS;
+ }
+ else if (information is FileRenameInformationType2)
+ {
+ FileRenameInformationType2 renameInformation = (FileRenameInformationType2)information;
+ string destination = renameInformation.FileName;
+ if (!destination.StartsWith(@"\"))
+ {
+ destination = @"\" + destination;
+ }
+
+ if (openFile.Stream != null)
+ {
+ openFile.Stream.Close();
+ }
+
+ try
+ {
+ if (renameInformation.ReplaceIfExists && (fileSystem.GetEntry(destination) != null ))
+ {
+ fileSystem.Delete(destination);
+ }
+ fileSystem.Move(openFile.Path, destination);
+ }
+ catch (IOException ex)
+ {
+ ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex);
+ if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION)
+ {
+ state.LogToServer(Severity.Debug, "SetFileInformation: Cannot rename '{0}'. Sharing Violation.", openFile.Path);
+ return NTStatus.STATUS_SHARING_VIOLATION;
+ }
+ if (errorCode == (ushort)Win32Error.ERROR_ALREADY_EXISTS)
+ {
+ state.LogToServer(Severity.Debug, "SetFileInformation: Cannot rename '{0}'. Already Exists.", openFile.Path);
+ return NTStatus.STATUS_OBJECT_NAME_EXISTS;
+ }
+ else
+ {
+ state.LogToServer(Severity.Debug, "SetFileInformation: Cannot rename '{0}'. Data Error.", openFile.Path);
+ return NTStatus.STATUS_DATA_ERROR;
+ }
+ }
+ catch (UnauthorizedAccessException)
+ {
+ state.LogToServer(Severity.Debug, "SetFileInformation: Cannot rename '{0}'. Access Denied.", openFile.Path);
+ return NTStatus.STATUS_ACCESS_DENIED;
+ }
+ openFile.Path = destination;
+ return NTStatus.STATUS_SUCCESS;
+ }
+ else if (information is FileDispositionInformation)
+ {
+ if (((FileDispositionInformation)information).DeletePending)
+ {
+ // We're supposed to delete the file on close, but it's too late to report errors at this late stage
+ if (openFile.Stream != null)
+ {
+ openFile.Stream.Close();
+ }
+
+ try
+ {
+ state.LogToServer(Severity.Information, "SetFileInformation: Deleting file '{0}'", openFile.Path);
+ fileSystem.Delete(openFile.Path);
+ }
+ catch (IOException ex)
+ {
+ ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex);
+ if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION)
+ {
+ state.LogToServer(Severity.Information, "SetFileInformation: Error deleting '{0}'. Sharing Violation.", openFile.Path);
+ return NTStatus.STATUS_SHARING_VIOLATION;
+ }
+ else
+ {
+ state.LogToServer(Severity.Information, "SetFileInformation: Error deleting '{0}'. Data Error.", openFile.Path);
+ return NTStatus.STATUS_DATA_ERROR;
+ }
+ }
+ catch (UnauthorizedAccessException)
+ {
+ state.LogToServer(Severity.Information, "SetFileInformation: Error deleting '{0}', Access Denied.", openFile.Path);
+ return NTStatus.STATUS_ACCESS_DENIED;
+ }
+ }
+ return NTStatus.STATUS_SUCCESS;
+ }
+ else if (information is FileAllocationInformation)
+ {
+ ulong allocationSize = ((FileAllocationInformation)information).AllocationSize;
+ try
+ {
+ openFile.Stream.SetLength((long)allocationSize);
+ }
+ catch (IOException ex)
+ {
+ ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex);
+ if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION)
+ {
+ state.LogToServer(Severity.Debug, "SetFileInformation: Cannot set allocation for '{0}'. Sharing Violation.", openFile.Path);
+ return NTStatus.STATUS_SHARING_VIOLATION;
+ }
+ else
+ {
+ state.LogToServer(Severity.Debug, "SetFileInformation: Cannot set allocation for '{0}'. Data Error.", openFile.Path);
+ return NTStatus.STATUS_DATA_ERROR;
+ }
+ }
+ catch (UnauthorizedAccessException)
+ {
+ state.LogToServer(Severity.Debug, "SetFileInformation: Cannot set allocation for '{0}'. Access Denied.", openFile.Path);
+ return NTStatus.STATUS_ACCESS_DENIED;
+ }
+ return NTStatus.STATUS_SUCCESS;
+ }
+ else if (information is FileEndOfFileInformation)
+ {
+ ulong endOfFile = ((FileEndOfFileInformation)information).EndOfFile;
+ try
+ {
+ openFile.Stream.SetLength((long)endOfFile);
+ }
+ catch (IOException ex)
+ {
+ ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex);
+ if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION)
+ {
+ state.LogToServer(Severity.Debug, "SetFileInformation: Cannot set end of file for '{0}'. Sharing Violation.", openFile.Path);
+ return NTStatus.STATUS_SHARING_VIOLATION;
+ }
+ else
+ {
+ state.LogToServer(Severity.Debug, "SetFileInformation: Cannot set end of file for '{0}'. Data Error.", openFile.Path);
+ return NTStatus.STATUS_DATA_ERROR;
+ }
+ }
+ catch (UnauthorizedAccessException)
+ {
+ state.LogToServer(Severity.Debug, "SetFileInformation: Cannot set end of file for '{0}'. Access Denied.", openFile.Path);
+ return NTStatus.STATUS_ACCESS_DENIED;
+ }
+ return NTStatus.STATUS_SUCCESS;
+ }
+ else
+ {
+ return NTStatus.STATUS_NOT_IMPLEMENTED;
+ }
+ }
+ }
+}
diff --git a/SMBLibrary/Server/SMB2/CreateHelper.cs b/SMBLibrary/Server/SMB2/CreateHelper.cs
new file mode 100644
index 0000000..eb16ab1
--- /dev/null
+++ b/SMBLibrary/Server/SMB2/CreateHelper.cs
@@ -0,0 +1,149 @@
+/* Copyright (C) 2017 Tal Aloni . 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.IO;
+using SMBLibrary.SMB2;
+using Utilities;
+
+namespace SMBLibrary.Server.SMB2
+{
+ public class CreateHelper
+ {
+ internal static SMB2Command GetCreateResponse(CreateRequest request, ISMBShare share, SMB2ConnectionState state)
+ {
+ SMB2Session session = state.GetSession(request.Header.SessionID);
+ string path = request.Name;
+ if (!path.StartsWith(@"\"))
+ {
+ path = @"\" + path;
+ }
+ if (share is NamedPipeShare)
+ {
+ Stream pipeStream = ((NamedPipeShare)share).OpenPipe(path);
+ if (pipeStream != null)
+ {
+ ulong? persistentFileID = session.AddOpenFile(path, pipeStream);
+ if (!persistentFileID.HasValue)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_TOO_MANY_OPENED_FILES);
+ }
+ return CreateResponseForNamedPipe(persistentFileID.Value);
+ }
+ else
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_OBJECT_PATH_NOT_FOUND);
+ }
+ }
+ else
+ {
+ FileSystemShare fileSystemShare = (FileSystemShare)share;
+
+ FileSystemEntry entry;
+ NTStatus createStatus = NTFileSystemHelper.CreateFile(out entry, (FileSystemShare)share, session.UserName, path, request.CreateDisposition, request.CreateOptions, request.DesiredAccess, state);
+ if (createStatus != NTStatus.STATUS_SUCCESS)
+ {
+ return new ErrorResponse(request.CommandName, createStatus);
+ }
+
+ IFileSystem fileSystem = fileSystemShare.FileSystem;
+ FileAccess fileAccess = NTFileSystemHelper.ToFileAccess(request.DesiredAccess.File);
+ FileShare fileShare = NTFileSystemHelper.ToFileShare(request.ShareAccess);
+
+ Stream stream;
+ bool deleteOnClose = false;
+ if (fileAccess == (FileAccess)0 || entry.IsDirectory)
+ {
+ stream = null;
+ }
+ else
+ {
+ // When FILE_OPEN_REPARSE_POINT is specified, the operation should continue normally if the file is not a reparse point.
+ // FILE_OPEN_REPARSE_POINT is a hint that the caller does not intend to actually read the file, with the exception
+ // of a file copy operation (where the caller will attempt to simply copy the reparse point).
+ deleteOnClose = (request.CreateOptions & CreateOptions.FILE_DELETE_ON_CLOSE) > 0;
+ bool openReparsePoint = (request.CreateOptions & CreateOptions.FILE_OPEN_REPARSE_POINT) > 0;
+ bool disableBuffering = (request.CreateOptions & CreateOptions.FILE_NO_INTERMEDIATE_BUFFERING) > 0;
+ bool buffered = (request.CreateOptions & CreateOptions.FILE_SEQUENTIAL_ONLY) > 0 && !disableBuffering && !openReparsePoint;
+ state.LogToServer(Severity.Verbose, "Create: Opening '{0}', Access={1}, Share={2}, Buffered={3}", path, fileAccess, fileShare, buffered);
+ try
+ {
+ stream = fileSystem.OpenFile(path, FileMode.Open, fileAccess, fileShare);
+ }
+ catch (IOException ex)
+ {
+ ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex);
+ if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION)
+ {
+ state.LogToServer(Severity.Debug, "NTCreate: Sharing violation opening '{0}'", path);
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_SHARING_VIOLATION);
+ }
+ else
+ {
+ state.LogToServer(Severity.Debug, "NTCreate: Sharing violation opening '{0}', Data Error", path);
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_DATA_ERROR);
+ }
+ }
+ catch (UnauthorizedAccessException)
+ {
+ state.LogToServer(Severity.Debug, "NTCreate: Sharing violation opening '{0}', Access Denied", path);
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_ACCESS_DENIED);
+ }
+
+ if (buffered)
+ {
+ stream = new PrefetchedStream(stream);
+ }
+ }
+
+ ulong? persistentFileID = session.AddOpenFile(path, stream, deleteOnClose);
+ if (!persistentFileID.HasValue)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_TOO_MANY_OPENED_FILES);
+ }
+
+ CreateResponse response = CreateResponseFromFileSystemEntry(entry, persistentFileID.Value);
+ if (request.RequestedOplockLevel == OplockLevel.Batch)
+ {
+ response.OplockLevel = OplockLevel.Batch;
+ }
+ return response;
+ }
+ }
+
+ private static CreateResponse CreateResponseForNamedPipe(ulong persistentFileID)
+ {
+ CreateResponse response = new CreateResponse();
+ response.FileId.Persistent = persistentFileID;
+ response.CreateAction = CreateAction.FILE_OPENED;
+ response.FileAttributes = FileAttributes.Normal;
+ return response;
+ }
+
+ private static CreateResponse CreateResponseFromFileSystemEntry(FileSystemEntry entry, ulong persistentFileID)
+ {
+ CreateResponse response = new CreateResponse();
+ if (entry.IsDirectory)
+ {
+ response.FileAttributes = FileAttributes.Directory;
+ }
+ else
+ {
+ response.FileAttributes = FileAttributes.Normal;
+ }
+ response.FileId.Persistent = persistentFileID;
+ response.CreateAction = CreateAction.FILE_OPENED;
+ response.CreationTime = entry.CreationTime;
+ response.LastWriteTime = entry.LastWriteTime;
+ response.ChangeTime = entry.LastWriteTime;
+ response.LastAccessTime = entry.LastAccessTime;
+ response.AllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size);
+ response.EndofFile = entry.Size;
+ return response;
+ }
+ }
+}
diff --git a/SMBLibrary/Server/SMB2/IOCtlHelper.cs b/SMBLibrary/Server/SMB2/IOCtlHelper.cs
new file mode 100644
index 0000000..044ed69
--- /dev/null
+++ b/SMBLibrary/Server/SMB2/IOCtlHelper.cs
@@ -0,0 +1,51 @@
+/* Copyright (C) 2017 Tal Aloni . 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.IO;
+using SMBLibrary.SMB2;
+using Utilities;
+
+namespace SMBLibrary.Server.SMB2
+{
+ public class IOCtlHelper
+ {
+ private const uint FSCTL_DFS_GET_REFERRALS = 0x00060194;
+ private const uint FSCTL_DFS_GET_REFERRALS_EX = 0x000601B0;
+ private const uint FSCTL_PIPE_TRANSCEIVE = 0x0011C017;
+
+ internal static SMB2Command GetIOCtlResponse(IOCtlRequest request, ISMBShare share, SMB2ConnectionState state)
+ {
+ SMB2Session session = state.GetSession(request.Header.SessionID);
+ if (request.CtlCode == FSCTL_DFS_GET_REFERRALS || request.CtlCode == FSCTL_DFS_GET_REFERRALS_EX)
+ {
+ // [MS-SMB2] 3.3.5.15.2 Handling a DFS Referral Information Request
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_FS_DRIVER_REQUIRED);
+ }
+
+ OpenFileObject openFile = session.GetOpenFileObject(request.FileId.Persistent);
+ if (openFile == null)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_FILE_CLOSED);
+ }
+
+ if (share is NamedPipeShare)
+ {
+ if (request.CtlCode == FSCTL_PIPE_TRANSCEIVE)
+ {
+ IOCtlResponse response = new IOCtlResponse();
+ response.CtlCode = request.CtlCode;
+ openFile.Stream.Write(request.Input, 0, request.Input.Length);
+ response.Output = ByteReader.ReadAllBytes(openFile.Stream);
+ return response;
+ }
+ }
+
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_NOT_SUPPORTED);
+ }
+ }
+}
diff --git a/SMBLibrary/Server/SMB2/NegotiateHelper.cs b/SMBLibrary/Server/SMB2/NegotiateHelper.cs
new file mode 100644
index 0000000..d79a218
--- /dev/null
+++ b/SMBLibrary/Server/SMB2/NegotiateHelper.cs
@@ -0,0 +1,101 @@
+/* Copyright (C) 2017 Tal Aloni . 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 SMBLibrary.Authentication;
+using SMBLibrary.SMB2;
+using Utilities;
+
+namespace SMBLibrary.Server.SMB2
+{
+ ///
+ /// Negotiate helper
+ ///
+ public class NegotiateHelper
+ {
+ public const string SMB2002Dialect = "SMB 2.002";
+ public const string SMB2xxxDialect = "SMB 2.???";
+
+ // Special case - SMB2 client initially connecting using SMB1
+ internal static SMB2Command GetNegotiateResponse(List smb2Dialects, ConnectionState state, Guid serverGuid)
+ {
+ NegotiateResponse response = new NegotiateResponse();
+ response.Header.Credits = 1;
+
+ if (smb2Dialects.Contains(SMB2xxxDialect))
+ {
+ response.DialectRevision = SMB2Dialect.SMB2xx;
+ }
+ else if (smb2Dialects.Contains(SMB2002Dialect))
+ {
+ state.ServerDialect = SMBDialect.SMB202;
+ response.DialectRevision = SMB2Dialect.SMB202;
+ }
+ else
+ {
+ throw new ArgumentException("SMB2 dialect is not present");
+ }
+ response.ServerGuid = serverGuid;
+ response.MaxTransactSize = 65536;
+ response.MaxReadSize = 65536;
+ response.MaxWriteSize = 65536;
+ response.SystemTime = DateTime.Now;
+ response.ServerStartTime = DateTime.Today;
+ return response;
+ }
+
+ internal static SMB2Command GetNegotiateResponse(NegotiateRequest request, ConnectionState state, Guid serverGuid)
+ {
+ NegotiateResponse response = new NegotiateResponse();
+ if (request.Dialects.Contains(SMB2Dialect.SMB210))
+ {
+ state.ServerDialect = SMBDialect.SMB210;
+ response.DialectRevision = SMB2Dialect.SMB210;
+ }
+ else if (request.Dialects.Contains(SMB2Dialect.SMB202))
+ {
+ state.ServerDialect = SMBDialect.SMB202;
+ response.DialectRevision = SMB2Dialect.SMB202;
+ }
+ else
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_NOT_SUPPORTED);
+ }
+ response.ServerGuid = serverGuid;
+ response.MaxTransactSize = 65536;
+ response.MaxReadSize = 65536;
+ response.MaxWriteSize = 65536;
+ response.SystemTime = DateTime.Now;
+ response.ServerStartTime = DateTime.Today;
+ return response;
+ }
+
+ internal static List FindSMB2Dialects(SMBLibrary.SMB1.SMB1Message message)
+ {
+ if (message.Commands.Count > 0 && message.Commands[0] is SMBLibrary.SMB1.NegotiateRequest)
+ {
+ SMBLibrary.SMB1.NegotiateRequest request = (SMBLibrary.SMB1.NegotiateRequest)message.Commands[0];
+ return FindSMB2Dialects(request);
+ }
+ return new List();
+ }
+
+ internal static List FindSMB2Dialects(SMBLibrary.SMB1.NegotiateRequest request)
+ {
+ List result = new List();
+ if (request.Dialects.Contains(SMB2002Dialect))
+ {
+ result.Add(SMB2002Dialect);
+ }
+ if (request.Dialects.Contains(SMB2xxxDialect))
+ {
+ result.Add(SMB2xxxDialect);
+ }
+ return result;
+ }
+ }
+}
diff --git a/SMBLibrary/Server/SMB2/QueryDirectoryHelper.cs b/SMBLibrary/Server/SMB2/QueryDirectoryHelper.cs
new file mode 100644
index 0000000..7f8e30f
--- /dev/null
+++ b/SMBLibrary/Server/SMB2/QueryDirectoryHelper.cs
@@ -0,0 +1,208 @@
+/* Copyright (C) 2017 Tal Aloni . 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 SMBLibrary.Authentication;
+using SMBLibrary.SMB2;
+using Utilities;
+
+namespace SMBLibrary.Server.SMB2
+{
+ public class QueryDirectoryHelper
+ {
+ internal static SMB2Command GetQueryDirectoryResponse(QueryDirectoryRequest request, ISMBShare share, SMB2ConnectionState state)
+ {
+ if (!(share is FileSystemShare))
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_NOT_SUPPORTED);
+ }
+
+ SMB2Session session = state.GetSession(request.Header.SessionID);
+ OpenFileObject openFile = session.GetOpenFileObject(request.FileId.Persistent);
+ if (openFile == null)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_FILE_CLOSED);
+ }
+
+ FileSystemShare fileSystemShare = (FileSystemShare)share;
+ IFileSystem fileSystem = fileSystemShare.FileSystem;
+
+ if (!fileSystem.GetEntry(openFile.Path).IsDirectory)
+ {
+ if ((request.Flags & QueryDirectoryFlags.SMB2_REOPEN) > 0)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_NOT_SUPPORTED);
+ }
+ else
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_INVALID_PARAMETER);
+ }
+ }
+
+ ulong fileID = request.FileId.Persistent;
+ OpenSearch openSearch = session.GetOpenSearch(fileID);
+ if (openSearch == null || request.Reopen)
+ {
+ if (request.Reopen)
+ {
+ session.RemoveOpenSearch(fileID);
+ }
+ List entries;
+ NTStatus searchStatus = NTFileSystemHelper.FindEntries(out entries, fileSystemShare.FileSystem, openFile.Path, request.FileName);
+ if (searchStatus != NTStatus.STATUS_SUCCESS)
+ {
+ state.LogToServer(Severity.Verbose, "Query Directory: Path: '{0}', Searched for '{1}', NTStatus: {2}", openFile.Path, request.FileName, searchStatus.ToString());
+ return new ErrorResponse(request.CommandName, searchStatus);
+ }
+ state.LogToServer(Severity.Verbose, "Query Directory: Path: '{0}', Searched for '{1}', found {2} matching entries", openFile.Path, request.FileName, entries.Count);
+ openSearch = session.AddOpenSearch(fileID, entries, 0);
+ }
+
+ if (request.Restart || request.Reopen)
+ {
+ openSearch.EnumerationLocation = 0;
+ }
+
+ if (openSearch.Entries.Count == 0)
+ {
+ // [MS-SMB2] If there are no entries to return [..] the server MUST fail the request with STATUS_NO_SUCH_FILE.
+ session.RemoveOpenSearch(fileID);
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_NO_SUCH_FILE);
+ }
+
+ if (openSearch.EnumerationLocation == openSearch.Entries.Count)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_NO_MORE_FILES);
+ }
+
+ List page = new List();
+ int pageLength = 0;
+ for (int index = openSearch.EnumerationLocation; index < openSearch.Entries.Count; index++)
+ {
+ QueryDirectoryFileInformation fileInformation;
+ try
+ {
+ fileInformation = FromFileSystemEntry(openSearch.Entries[index], request.FileInformationClass);
+ }
+ catch (UnsupportedInformationLevelException)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_INVALID_INFO_CLASS);
+ }
+
+ if (pageLength + fileInformation.Length <= request.OutputBufferLength)
+ {
+ page.Add(fileInformation);
+ pageLength += fileInformation.Length;
+ openSearch.EnumerationLocation = index + 1;
+ }
+ else
+ {
+ break;
+ }
+
+ if (request.ReturnSingleEntry)
+ {
+ break;
+ }
+ }
+
+ QueryDirectoryResponse response = new QueryDirectoryResponse();
+ response.SetFileInformationList(page);
+ return response;
+ }
+
+ internal static QueryDirectoryFileInformation FromFileSystemEntry(FileSystemEntry entry, FileInformationClass informationClass)
+ {
+ switch (informationClass)
+ {
+ case FileInformationClass.FileBothDirectoryInformation:
+ {
+ FileBothDirectoryInformation result = new FileBothDirectoryInformation();
+ result.CreationTime = entry.CreationTime;
+ result.LastAccessTime = entry.LastAccessTime;
+ result.LastWriteTime = entry.LastWriteTime;
+ result.ChangeTime = entry.LastWriteTime;
+ result.EndOfFile = entry.Size;
+ result.AllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size);
+ result.FileAttributes = NTFileSystemHelper.GetFileAttributes(entry);
+ result.EaSize = 0;
+ result.ShortName = NTFileSystemHelper.GetShortName(entry.Name);
+ result.FileName = entry.Name;
+ return result;
+ }
+ case FileInformationClass.FileDirectoryInformation:
+ {
+ FileDirectoryInformation result = new FileDirectoryInformation();
+ result.CreationTime = entry.CreationTime;
+ result.LastAccessTime = entry.LastAccessTime;
+ result.LastWriteTime = entry.LastWriteTime;
+ result.ChangeTime = entry.LastWriteTime;
+ result.EndOfFile = entry.Size;
+ result.AllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size);
+ result.FileAttributes = NTFileSystemHelper.GetFileAttributes(entry);
+ result.FileName = entry.Name;
+ return result;
+ }
+ case FileInformationClass.FileFullDirectoryInformation:
+ {
+ FileFullDirectoryInformation result = new FileFullDirectoryInformation();
+ result.CreationTime = entry.CreationTime;
+ result.LastAccessTime = entry.LastAccessTime;
+ result.LastWriteTime = entry.LastWriteTime;
+ result.ChangeTime = entry.LastWriteTime;
+ result.EndOfFile = entry.Size;
+ result.AllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size);
+ result.FileAttributes = NTFileSystemHelper.GetFileAttributes(entry);
+ result.EaSize = 0;
+ result.FileName = entry.Name;
+ return result;
+ }
+ case FileInformationClass.FileIdBothDirectoryInformation:
+ {
+ FileIdBothDirectoryInformation result = new FileIdBothDirectoryInformation();
+ result.CreationTime = entry.CreationTime;
+ result.LastAccessTime = entry.LastAccessTime;
+ result.LastWriteTime = entry.LastWriteTime;
+ result.ChangeTime = entry.LastWriteTime;
+ result.EndOfFile = entry.Size;
+ result.AllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size);
+ result.FileAttributes = NTFileSystemHelper.GetFileAttributes(entry);
+ result.EaSize = 0;
+ result.ShortName = NTFileSystemHelper.GetShortName(entry.Name);
+ result.FileId = 0;
+ result.FileName = entry.Name;
+ return result;
+ }
+ case FileInformationClass.FileIdFullDirectoryInformation:
+ {
+ FileIdFullDirectoryInformation result = new FileIdFullDirectoryInformation();
+ result.CreationTime = entry.CreationTime;
+ result.LastAccessTime = entry.LastAccessTime;
+ result.LastWriteTime = entry.LastWriteTime;
+ result.ChangeTime = entry.LastWriteTime;
+ result.EndOfFile = entry.Size;
+ result.AllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size);
+ result.FileAttributes = NTFileSystemHelper.GetFileAttributes(entry);
+ result.EaSize = 0;
+ result.FileId = 0;
+ result.FileName = entry.Name;
+ return result;
+ }
+ case FileInformationClass.FileNamesInformation:
+ {
+ FileNamesInformation result = new FileNamesInformation();
+ result.FileName = entry.Name;
+ return result;
+ }
+ default:
+ {
+ throw new UnsupportedInformationLevelException();
+ }
+ }
+ }
+ }
+}
diff --git a/SMBLibrary/Server/SMB2/QueryInfoHelper.cs b/SMBLibrary/Server/SMB2/QueryInfoHelper.cs
new file mode 100644
index 0000000..bbd820c
--- /dev/null
+++ b/SMBLibrary/Server/SMB2/QueryInfoHelper.cs
@@ -0,0 +1,75 @@
+/* Copyright (C) 2017 Tal Aloni . 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 SMBLibrary.Authentication;
+using SMBLibrary.SMB2;
+using Utilities;
+
+namespace SMBLibrary.Server.SMB2
+{
+ public class QueryInfoHelper
+ {
+ internal static SMB2Command GetQueryInfoResponse(QueryInfoRequest request, ISMBShare share, SMB2ConnectionState state)
+ {
+ SMB2Session session = state.GetSession(request.Header.SessionID);
+ if (request.InfoType == InfoType.File)
+ {
+ OpenFileObject openFile = session.GetOpenFileObject(request.FileId.Persistent);
+ if (openFile == null)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_FILE_CLOSED);
+ }
+
+ FileInformation fileInformation;
+ NTStatus queryStatus;
+ if (share is NamedPipeShare)
+ {
+ queryStatus = NTFileSystemHelper.GetNamedPipeInformation(out fileInformation, request.FileInformationClass);
+ }
+ else // FileSystemShare
+ {
+ IFileSystem fileSystem = ((FileSystemShare)share).FileSystem;
+ FileSystemEntry entry = fileSystem.GetEntry(openFile.Path);
+ if (entry == null)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_NO_SUCH_FILE);
+ }
+ queryStatus = NTFileSystemHelper.GetFileInformation(out fileInformation, entry, openFile.DeleteOnClose, request.FileInformationClass);
+ }
+
+ if (queryStatus != NTStatus.STATUS_SUCCESS)
+ {
+ state.LogToServer(Severity.Verbose, "GetFileInformation on '{0}' failed. Information class: {1}, NTStatus: {2}", openFile.Path, request.FileInformationClass, queryStatus);
+ return new ErrorResponse(request.CommandName, queryStatus);
+ }
+
+ QueryInfoResponse response = new QueryInfoResponse();
+ response.SetFileInformation(fileInformation);
+ return response;
+ }
+ else if (request.InfoType == InfoType.FileSystem)
+ {
+ if (share is FileSystemShare)
+ {
+ IFileSystem fileSystem = ((FileSystemShare)share).FileSystem;
+ FileSystemInformation fileSystemInformation;
+ NTStatus queryStatus = NTFileSystemHelper.GetFileSystemInformation(out fileSystemInformation, request.FileSystemInformationClass, fileSystem);
+ if (queryStatus != NTStatus.STATUS_SUCCESS)
+ {
+ state.LogToServer(Severity.Verbose, "GetFileSystemInformation failed. Information class: {0}, NTStatus: {1}", request.FileSystemInformationClass, queryStatus);
+ return new ErrorResponse(request.CommandName, queryStatus);
+ }
+ QueryInfoResponse response = new QueryInfoResponse();
+ response.SetFileSystemInformation(fileSystemInformation);
+ return response;
+ }
+ }
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_NOT_SUPPORTED);
+ }
+ }
+}
diff --git a/SMBLibrary/Server/SMB2/ReadWriteResponseHelper.cs b/SMBLibrary/Server/SMB2/ReadWriteResponseHelper.cs
new file mode 100644
index 0000000..3a8bec0
--- /dev/null
+++ b/SMBLibrary/Server/SMB2/ReadWriteResponseHelper.cs
@@ -0,0 +1,57 @@
+/* Copyright (C) 2017 Tal Aloni . 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 SMBLibrary.Authentication;
+using SMBLibrary.SMB2;
+using Utilities;
+
+namespace SMBLibrary.Server.SMB2
+{
+ public class ReadWriteResponseHelper
+ {
+ internal static SMB2Command GetReadResponse(ReadRequest request, ISMBShare share, SMB2ConnectionState state)
+ {
+ SMB2Session session = state.GetSession(request.Header.SessionID);
+ OpenFileObject openFile = session.GetOpenFileObject(request.FileId.Persistent);
+ if (openFile == null)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_FILE_CLOSED);
+ }
+
+ byte[] data;
+ NTStatus readStatus = NTFileSystemHelper.ReadFile(out data, openFile, (long)request.Offset, (int)request.ReadLength, state);
+ if (readStatus != NTStatus.STATUS_SUCCESS)
+ {
+ return new ErrorResponse(request.CommandName, readStatus);
+ }
+ ReadResponse response = new ReadResponse();
+ response.Data = data;
+ return response;
+ }
+
+ internal static SMB2Command GetWriteResponse(WriteRequest request, ISMBShare share, SMB2ConnectionState state)
+ {
+ SMB2Session session = state.GetSession(request.Header.SessionID);
+ OpenFileObject openFile = session.GetOpenFileObject(request.FileId.Persistent);
+ if (openFile == null)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_FILE_CLOSED);
+ }
+
+ int numberOfBytesWritten;
+ NTStatus writeStatus = NTFileSystemHelper.WriteFile(out numberOfBytesWritten, openFile, (long)request.Offset, request.Data, state);
+ if (writeStatus != NTStatus.STATUS_SUCCESS)
+ {
+ return new ErrorResponse(request.CommandName, writeStatus);
+ }
+ WriteResponse response = new WriteResponse();
+ response.Count = (uint)numberOfBytesWritten;
+ return response;
+ }
+ }
+}
diff --git a/SMBLibrary/Server/SMB2/SessionSetupHelper.cs b/SMBLibrary/Server/SMB2/SessionSetupHelper.cs
new file mode 100644
index 0000000..39880cd
--- /dev/null
+++ b/SMBLibrary/Server/SMB2/SessionSetupHelper.cs
@@ -0,0 +1,101 @@
+/* Copyright (C) 2017 Tal Aloni . 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 SMBLibrary.Authentication;
+using SMBLibrary.SMB2;
+using Utilities;
+
+namespace SMBLibrary.Server.SMB2
+{
+ ///
+ /// Session Setup helper
+ ///
+ public class SessionSetupHelper
+ {
+ internal static SMB2Command GetSessionSetupResponse(SessionSetupRequest request, INTLMAuthenticationProvider users, SMB2ConnectionState state)
+ {
+ // [MS-SMB2] Windows [..] will also accept raw Kerberos messages and implicit NTLM messages as part of GSS authentication.
+ SessionSetupResponse response = new SessionSetupResponse();
+ byte[] messageBytes = request.SecurityBuffer;
+ bool isRawMessage = true;
+ if (!AuthenticationMessageUtils.IsSignatureValid(messageBytes))
+ {
+ messageBytes = GSSAPIHelper.GetNTLMSSPMessage(request.SecurityBuffer);
+ isRawMessage = false;
+ }
+ if (!AuthenticationMessageUtils.IsSignatureValid(messageBytes))
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_NOT_SUPPORTED);
+ }
+
+ // According to [MS-SMB2] 3.3.5.5.3, response.Header.SessionID must be allocated if the server returns STATUS_MORE_PROCESSING_REQUIRED
+ if (request.Header.SessionID == 0)
+ {
+ ulong? sessionID = state.AllocateSessionID();
+ if (!sessionID.HasValue)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_TOO_MANY_SESSIONS);
+ }
+ response.Header.SessionID = sessionID.Value;
+ }
+
+ MessageTypeName messageType = AuthenticationMessageUtils.GetMessageType(messageBytes);
+ if (messageType == MessageTypeName.Negotiate)
+ {
+ NegotiateMessage negotiateMessage = new NegotiateMessage(messageBytes);
+ ChallengeMessage challengeMessage = users.GetChallengeMessage(negotiateMessage);
+ if (isRawMessage)
+ {
+ response.SecurityBuffer = challengeMessage.GetBytes();
+ }
+ else
+ {
+ response.SecurityBuffer = GSSAPIHelper.GetGSSTokenResponseBytesFromNTLMSSPMessage(challengeMessage.GetBytes());
+ }
+ response.Header.Status = NTStatus.STATUS_MORE_PROCESSING_REQUIRED;
+ }
+ else // MessageTypeName.Authenticate
+ {
+ AuthenticateMessage authenticateMessage = new AuthenticateMessage(messageBytes);
+ bool loginSuccess;
+ try
+ {
+ loginSuccess = users.Authenticate(authenticateMessage);
+ }
+ catch (EmptyPasswordNotAllowedException)
+ {
+ state.LogToServer(Severity.Information, "User '{0}' authentication using an empty password was rejected", authenticateMessage.UserName);
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_ACCOUNT_RESTRICTION);
+ }
+
+ if (loginSuccess)
+ {
+ state.LogToServer(Severity.Information, "User '{0}' authenticated successfully", authenticateMessage.UserName);
+ state.CreateSession(request.Header.SessionID, authenticateMessage.UserName);
+ }
+ else if (users.FallbackToGuest(authenticateMessage.UserName))
+ {
+ state.LogToServer(Severity.Information, "User '{0}' failed authentication. logged in as guest", authenticateMessage.UserName);
+ state.CreateSession(request.Header.SessionID, "Guest");
+ response.SessionFlags = SessionFlags.IsGuest;
+ }
+ else
+ {
+ state.LogToServer(Severity.Information, "User '{0}' failed authentication", authenticateMessage.UserName);
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_LOGON_FAILURE);
+ }
+
+ if (!isRawMessage)
+ {
+ response.SecurityBuffer = GSSAPIHelper.GetGSSTokenAcceptCompletedResponse();
+ }
+ }
+ return response;
+ }
+ }
+}
diff --git a/SMBLibrary/Server/SMB2/SetInfoHelper.cs b/SMBLibrary/Server/SMB2/SetInfoHelper.cs
new file mode 100644
index 0000000..86120ab
--- /dev/null
+++ b/SMBLibrary/Server/SMB2/SetInfoHelper.cs
@@ -0,0 +1,65 @@
+/* Copyright (C) 2017 Tal Aloni . 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 SMBLibrary.Authentication;
+using SMBLibrary.SMB2;
+using Utilities;
+
+namespace SMBLibrary.Server.SMB2
+{
+ public class SetInfoHelper
+ {
+ internal static SMB2Command GetSetInfoResponse(SetInfoRequest request, ISMBShare share, SMB2ConnectionState state)
+ {
+ SMB2Session session = state.GetSession(request.Header.SessionID);
+ if (request.InfoType == InfoType.File)
+ {
+ OpenFileObject openFile = session.GetOpenFileObject(request.FileId.Persistent);
+ if (openFile == null)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_FILE_CLOSED);
+ }
+
+ if (share is FileSystemShare)
+ {
+ IFileSystem fileSystem = ((FileSystemShare)share).FileSystem;
+ if (!((FileSystemShare)share).HasWriteAccess(session.UserName))
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_ACCESS_DENIED);
+ }
+ FileInformation information;
+ try
+ {
+ information = FileInformation.GetFileInformation(request.Buffer, 0, request.FileInformationClass);
+ }
+ catch (UnsupportedInformationLevelException)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_INVALID_INFO_CLASS);
+ }
+ catch (NotImplementedException)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_NOT_SUPPORTED);
+ }
+ catch (Exception)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_INVALID_PARAMETER);
+ }
+
+ NTStatus status = NTFileSystemHelper.SetFileInformation(fileSystem, openFile, information, state);
+ if (status != NTStatus.STATUS_SUCCESS)
+ {
+ state.LogToServer(Severity.Verbose, "SetFileInformation on '{0}' failed. Information class: {1}, NTStatus: {2}", openFile.Path, information.FileInformationClass, status);
+ return new ErrorResponse(request.CommandName, status);
+ }
+ return new SetInfoResponse();
+ }
+ }
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_NOT_SUPPORTED);
+ }
+ }
+}
diff --git a/SMBLibrary/Server/SMB2/TreeConnectHelper.cs b/SMBLibrary/Server/SMB2/TreeConnectHelper.cs
new file mode 100644
index 0000000..f32d02a
--- /dev/null
+++ b/SMBLibrary/Server/SMB2/TreeConnectHelper.cs
@@ -0,0 +1,62 @@
+/* Copyright (C) 2014-2017 Tal Aloni . 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.Text;
+using SMBLibrary.SMB2;
+using Utilities;
+
+namespace SMBLibrary.Server.SMB2
+{
+ public class TreeConnectHelper
+ {
+ internal static SMB2Command GetTreeConnectResponse(TreeConnectRequest request, SMB2ConnectionState state, NamedPipeShare services, ShareCollection shares)
+ {
+ SMB2Session session = state.GetSession(request.Header.SessionID);
+ TreeConnectResponse response = new TreeConnectResponse();
+ string shareName = ServerPathUtils.GetShareName(request.Path);
+ ISMBShare share;
+ ShareType shareType;
+ ShareFlags shareFlags = ShareFlags.ManualCaching;
+ if (String.Equals(shareName, NamedPipeShare.NamedPipeShareName, StringComparison.InvariantCultureIgnoreCase))
+ {
+ share = services;
+ shareType = ShareType.Pipe;
+ shareFlags = ShareFlags.NoCaching;
+ }
+ else
+ {
+ share = shares.GetShareFromName(shareName);
+ shareType = ShareType.Disk;
+ if (share == null)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_OBJECT_PATH_NOT_FOUND);
+ }
+
+ if (!((FileSystemShare)share).HasReadAccess(session.UserName))
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_ACCESS_DENIED);
+ }
+ }
+
+ uint? treeID = session.AddConnectedTree(share);
+ if (!treeID.HasValue)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_INSUFF_SERVER_RESOURCES);
+ }
+ response.Header.TreeID = treeID.Value;
+ response.ShareType = shareType;
+ response.ShareFlags = shareFlags;
+ response.MaximalAccess.File = FileAccessMask.FILE_READ_DATA | FileAccessMask.FILE_WRITE_DATA | FileAccessMask.FILE_APPEND_DATA |
+ FileAccessMask.FILE_READ_EA | FileAccessMask.FILE_WRITE_EA |
+ FileAccessMask.FILE_EXECUTE |
+ FileAccessMask.FILE_READ_ATTRIBUTES | FileAccessMask.FILE_WRITE_ATTRIBUTES |
+ FileAccessMask.DELETE | FileAccessMask.READ_CONTROL | FileAccessMask.WRITE_DAC | FileAccessMask.WRITE_OWNER | FileAccessMask.SYNCHRONIZE;
+ return response;
+ }
+ }
+}
diff --git a/SMBLibrary/Server/SMBServer.SMB1.cs b/SMBLibrary/Server/SMBServer.SMB1.cs
index e8de18a..1f96885 100644
--- a/SMBLibrary/Server/SMBServer.SMB1.cs
+++ b/SMBLibrary/Server/SMBServer.SMB1.cs
@@ -15,7 +15,7 @@ namespace SMBLibrary.Server
{
public partial class SMBServer
{
- public void ProcessSMB1Message(SMB1Message message, SMB1ConnectionState state)
+ public void ProcessSMB1Message(SMB1Message message, ref ConnectionState state)
{
SMB1Message reply = new SMB1Message();
PrepareResponseHeader(reply, message);
@@ -23,7 +23,7 @@ namespace SMBLibrary.Server
foreach (SMB1Command command in message.Commands)
{
- SMB1Command response = ProcessSMB1Command(reply.Header, command, state, sendQueue);
+ SMB1Command response = ProcessSMB1Command(reply.Header, command, ref state, sendQueue);
if (response != null)
{
reply.Commands.Add(response);
@@ -51,7 +51,7 @@ namespace SMBLibrary.Server
///
/// May return null
///
- public SMB1Command ProcessSMB1Command(SMB1Header header, SMB1Command command, SMB1ConnectionState state, List sendQueue)
+ public SMB1Command ProcessSMB1Command(SMB1Header header, SMB1Command command, ref ConnectionState state, List sendQueue)
{
if (state.ServerDialect == SMBDialect.NotSet)
{
@@ -60,6 +60,7 @@ namespace SMBLibrary.Server
NegotiateRequest request = (NegotiateRequest)command;
if (request.Dialects.Contains(SMBServer.NTLanManagerDialect))
{
+ state = new SMB1ConnectionState(state);
state.ServerDialect = SMBDialect.NTLM012;
if (EnableExtendedSecurity && header.ExtendedSecurityFlag)
{
@@ -89,7 +90,15 @@ namespace SMBLibrary.Server
header.Status = NTStatus.STATUS_SMB_BAD_COMMAND;
return new ErrorResponse(command.CommandName);
}
- else if (command is SessionSetupAndXRequest)
+ else
+ {
+ return ProcessSMB1Command(header, command, (SMB1ConnectionState)state, sendQueue);
+ }
+ }
+
+ private SMB1Command ProcessSMB1Command(SMB1Header header, SMB1Command command, SMB1ConnectionState state, List sendQueue)
+ {
+ if (command is SessionSetupAndXRequest)
{
SessionSetupAndXRequest request = (SessionSetupAndXRequest)command;
state.MaxBufferSize = request.MaxBufferSize;
@@ -329,12 +338,12 @@ namespace SMBLibrary.Server
return new ErrorResponse(command.CommandName);
}
- public static void TrySendMessage(SMB1ConnectionState state, SMB1Message response)
+ public static void TrySendMessage(ConnectionState state, SMB1Message response)
{
SessionMessagePacket packet = new SessionMessagePacket();
packet.Trailer = response.GetBytes();
TrySendPacket(state, packet);
- state.LogToServer(Severity.Verbose, "Response sent: {0} Commands, First Command: {1}, Packet length: {2}", response.Commands.Count, response.Commands[0].CommandName.ToString(), packet.Length);
+ state.LogToServer(Severity.Verbose, "SMB1 message sent: {0} responses, First response: {1}, Packet length: {2}", response.Commands.Count, response.Commands[0].CommandName.ToString(), packet.Length);
}
private static void PrepareResponseHeader(SMB1Message response, SMB1Message request)
diff --git a/SMBLibrary/Server/SMBServer.SMB2.cs b/SMBLibrary/Server/SMBServer.SMB2.cs
new file mode 100644
index 0000000..fc8cd38
--- /dev/null
+++ b/SMBLibrary/Server/SMBServer.SMB2.cs
@@ -0,0 +1,211 @@
+/* Copyright (C) 2017 Tal Aloni . 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 SMBLibrary.NetBios;
+using SMBLibrary.Server.SMB2;
+using SMBLibrary.SMB2;
+using Utilities;
+
+namespace SMBLibrary.Server
+{
+ public partial class SMBServer
+ {
+ // Key is the persistent portion of the FileID
+ private Dictionary m_globalOpenFiles = new Dictionary();
+ private static ulong m_nextPersistentFileID = 1; // A numeric value that uniquely identifies the open handle to a file or a pipe within the scope of all opens granted by the server
+
+ private ulong? AllocatePersistentFileID()
+ {
+ for (ulong offset = 0; offset < UInt64.MaxValue; offset++)
+ {
+ ulong persistentID = (ulong)(m_nextPersistentFileID + offset);
+ if (persistentID == 0 || persistentID == 0xFFFFFFFFFFFFFFFF)
+ {
+ continue;
+ }
+ if (!m_globalOpenFiles.ContainsKey(persistentID))
+ {
+ m_nextPersistentFileID = (ulong)(persistentID + 1);
+ return persistentID;
+ }
+ }
+ return null;
+ }
+
+ ///
+ /// May return null
+ ///
+ public SMB2Command ProcessSMB2Command(SMB2Command command, ref ConnectionState state)
+ {
+ if (state.ServerDialect == SMBDialect.NotSet)
+ {
+ if (command is NegotiateRequest)
+ {
+ NegotiateRequest request = (NegotiateRequest)command;
+ state = new SMB2ConnectionState(state, AllocatePersistentFileID);
+ return NegotiateHelper.GetNegotiateResponse(request, state, m_serverGuid);
+ }
+ else
+ {
+ // [MS-SMB2] If the request being received is not an SMB2 NEGOTIATE Request [..]
+ // and Connection.NegotiateDialect is 0xFFFF or 0x02FF, the server MUST
+ // disconnect the connection.
+ state.LogToServer(Severity.Debug, "Invalid Connection State for command {0}", command.CommandName.ToString());
+ state.ClientSocket.Close();
+ return null;
+ }
+ }
+ else if (command is NegotiateRequest)
+ {
+ // [MS-SMB2] If Connection.NegotiateDialect is 0x0202, 0x0210, 0x0300, 0x0302, or 0x0311,
+ // the server MUST disconnect the connection.
+ state.LogToServer(Severity.Debug, "Rejecting NegotiateRequest. NegotiateDialect is already set");
+ state.ClientSocket.Close();
+ return null;
+ }
+ else
+ {
+ return ProcessSMB2Command(command, (SMB2ConnectionState)state);
+ }
+ }
+
+ public SMB2Command ProcessSMB2Command(SMB2Command command, SMB2ConnectionState state)
+ {
+ if (command is SessionSetupRequest)
+ {
+ return SessionSetupHelper.GetSessionSetupResponse((SessionSetupRequest)command, m_users, state);
+ }
+ else if (command is EchoRequest)
+ {
+ return new EchoResponse();
+ }
+ else
+ {
+ SMB2Session session = state.GetSession(command.Header.SessionID);
+ if (session == null)
+ {
+ return new ErrorResponse(command.CommandName, NTStatus.STATUS_USER_SESSION_DELETED);
+ }
+
+ if (command is TreeConnectRequest)
+ {
+ return TreeConnectHelper.GetTreeConnectResponse((TreeConnectRequest)command, state, m_services, m_shares);
+ }
+ else if (command is LogoffRequest)
+ {
+ state.RemoveSession(command.Header.SessionID);
+ return new LogoffResponse();
+ }
+ else
+ {
+ ISMBShare share = session.GetConnectedTree(command.Header.TreeID);
+ if (share == null)
+ {
+ return new ErrorResponse(command.CommandName, NTStatus.STATUS_NETWORK_NAME_DELETED);
+ }
+
+ if (command is TreeDisconnectRequest)
+ {
+ session.RemoveConnectedTree(command.Header.TreeID);
+ return new TreeDisconnectResponse();
+ }
+ else if (command is CreateRequest)
+ {
+ return CreateHelper.GetCreateResponse((CreateRequest)command, share, state);
+ }
+ else if (command is QueryInfoRequest)
+ {
+ return QueryInfoHelper.GetQueryInfoResponse((QueryInfoRequest)command, share, state);
+ }
+ else if (command is SetInfoRequest)
+ {
+ return SetInfoHelper.GetSetInfoResponse((SetInfoRequest)command, share, state);
+ }
+ else if (command is QueryDirectoryRequest)
+ {
+ return QueryDirectoryHelper.GetQueryDirectoryResponse((QueryDirectoryRequest)command, share, state);
+ }
+ else if (command is ReadRequest)
+ {
+ return ReadWriteResponseHelper.GetReadResponse((ReadRequest)command, share, state);
+ }
+ else if (command is WriteRequest)
+ {
+ return ReadWriteResponseHelper.GetWriteResponse((WriteRequest)command, share, state);
+ }
+ else if (command is FlushRequest)
+ {
+ FlushRequest request = (FlushRequest)command;
+ OpenFileObject openFile = session.GetOpenFileObject(request.FileId.Persistent);
+ if (openFile == null)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_FILE_CLOSED);
+ }
+ openFile.Stream.Flush();
+ return new FlushResponse();
+ }
+ else if (command is CloseRequest)
+ {
+ CloseRequest request = (CloseRequest)command;
+ OpenFileObject openFile = session.GetOpenFileObject(request.FileId.Persistent);
+ if (openFile == null)
+ {
+ return new ErrorResponse(request.CommandName, NTStatus.STATUS_FILE_CLOSED);
+ }
+ session.RemoveOpenFile(request.FileId.Persistent);
+ return new CloseResponse();
+ }
+ else if (command is IOCtlRequest)
+ {
+ return IOCtlHelper.GetIOCtlResponse((IOCtlRequest)command, share, state);
+ }
+ else if (command is ChangeNotifyRequest)
+ {
+ // [MS-SMB2] If the underlying object store does not support change notifications, the server MUST fail this request with STATUS_NOT_SUPPORTED
+ return new ErrorResponse(command.CommandName, NTStatus.STATUS_NOT_SUPPORTED);
+ }
+ }
+ }
+
+ return new ErrorResponse(command.CommandName, NTStatus.STATUS_NOT_SUPPORTED);
+ }
+
+ public static void TrySendResponse(ConnectionState state, SMB2Command response)
+ {
+ SessionMessagePacket packet = new SessionMessagePacket();
+ packet.Trailer = response.GetBytes();
+ TrySendPacket(state, packet);
+ state.LogToServer(Severity.Verbose, "SMB2 response sent: {0}, Packet length: {1}", response.CommandName.ToString(), packet.Length);
+ }
+
+ public static void TrySendResponseChain(ConnectionState state, List responseChain)
+ {
+ SessionMessagePacket packet = new SessionMessagePacket();
+ packet.Trailer = SMB2Command.GetCommandChainBytes(responseChain);
+ TrySendPacket(state, packet);
+ state.LogToServer(Severity.Verbose, "SMB2 response chain sent: Response count: {0}, First response: {1}, Packet length: {2}", responseChain.Count, responseChain[0].CommandName.ToString(), packet.Length);
+ }
+
+ private static void UpdateSMB2Header(SMB2Command response, SMB2Command request)
+ {
+ response.Header.MessageID = request.Header.MessageID;
+ response.Header.CreditCharge = request.Header.CreditCharge;
+ response.Header.Credits = Math.Max((ushort)1, request.Header.Credits);
+ response.Header.IsRelatedOperations = request.Header.IsRelatedOperations;
+ response.Header.Reserved = request.Header.Reserved;
+ if (response.Header.SessionID == 0)
+ {
+ response.Header.SessionID = request.Header.SessionID;
+ }
+ if (response.Header.TreeID == 0)
+ {
+ response.Header.TreeID = request.Header.TreeID;
+ }
+ }
+ }
+}
diff --git a/SMBLibrary/Server/SMBServer.cs b/SMBLibrary/Server/SMBServer.cs
index b15d7d7..d93f4ef 100644
--- a/SMBLibrary/Server/SMBServer.cs
+++ b/SMBLibrary/Server/SMBServer.cs
@@ -11,6 +11,7 @@ using System.Net.Sockets;
using SMBLibrary.NetBios;
using SMBLibrary.Services;
using SMBLibrary.SMB1;
+using SMBLibrary.SMB2;
using Utilities;
namespace SMBLibrary.Server
@@ -27,6 +28,8 @@ namespace SMBLibrary.Server
private NamedPipeShare m_services; // Named pipes
private IPAddress m_serverAddress;
private SMBTransportType m_transport;
+ private bool m_enableSMB1;
+ private bool m_enableSMB2;
private Socket m_listenerSocket;
private bool m_listening;
@@ -34,13 +37,19 @@ namespace SMBLibrary.Server
public event EventHandler OnLogEntry;
- public SMBServer(ShareCollection shares, INTLMAuthenticationProvider users, IPAddress serverAddress, SMBTransportType transport)
+ public SMBServer(ShareCollection shares, INTLMAuthenticationProvider users, IPAddress serverAddress, SMBTransportType transport) : this(shares, users, serverAddress, transport, true, true)
+ {
+ }
+
+ public SMBServer(ShareCollection shares, INTLMAuthenticationProvider users, IPAddress serverAddress, SMBTransportType transport, bool enableSMB1, bool enableSMB2)
{
m_shares = shares;
m_users = users;
m_serverAddress = serverAddress;
m_serverGuid = Guid.NewGuid();
m_transport = transport;
+ m_enableSMB1 = enableSMB1;
+ m_enableSMB2 = enableSMB2;
m_services = new NamedPipeShare(shares.ListShares());
}
@@ -93,7 +102,7 @@ namespace SMBLibrary.Server
return;
}
- SMB1ConnectionState state = new SMB1ConnectionState(new ConnectionState(Log));
+ ConnectionState state = new ConnectionState(Log);
// Disable the Nagle Algorithm for this tcp socket:
clientSocket.NoDelay = true;
state.ClientSocket = clientSocket;
@@ -116,7 +125,7 @@ namespace SMBLibrary.Server
private void ReceiveCallback(IAsyncResult result)
{
- SMB1ConnectionState state = (SMB1ConnectionState)result.AsyncState;
+ ConnectionState state = (ConnectionState)result.AsyncState;
Socket clientSocket = state.ClientSocket;
if (!m_listening)
@@ -149,7 +158,7 @@ namespace SMBLibrary.Server
NBTConnectionReceiveBuffer receiveBuffer = state.ReceiveBuffer;
receiveBuffer.SetNumberOfBytesReceived(numberOfBytesReceived);
- ProcessConnectionBuffer(state);
+ ProcessConnectionBuffer(ref state);
if (clientSocket.Connected)
{
@@ -166,7 +175,7 @@ namespace SMBLibrary.Server
}
}
- public void ProcessConnectionBuffer(SMB1ConnectionState state)
+ public void ProcessConnectionBuffer(ref ConnectionState state)
{
Socket clientSocket = state.ClientSocket;
@@ -185,12 +194,12 @@ namespace SMBLibrary.Server
if (packet != null)
{
- ProcessPacket(packet, state);
+ ProcessPacket(packet, ref state);
}
}
}
- public void ProcessPacket(SessionPacket packet, SMB1ConnectionState state)
+ public void ProcessPacket(SessionPacket packet, ref ConnectionState state)
{
if (packet is SessionRequestPacket && m_transport == SMBTransportType.NetBiosOverTCP)
{
@@ -203,19 +212,97 @@ namespace SMBLibrary.Server
}
else if (packet is SessionMessagePacket)
{
- SMB1Message message = null;
- try
+ // Note: To be compatible with SMB2 specifications, we must accept SMB_COM_NEGOTIATE.
+ // We will disconnect the connection if m_enableSMB1 == false and the client does not support SMB2.
+ bool acceptSMB1 = (state.ServerDialect == SMBDialect.NotSet || state.ServerDialect == SMBDialect.NTLM012);
+ bool acceptSMB2 = (m_enableSMB2 && (state.ServerDialect == SMBDialect.NotSet || state.ServerDialect == SMBDialect.SMB202 || state.ServerDialect == SMBDialect.SMB210));
+
+ if (SMB1Header.IsValidSMB1Header(packet.Trailer))
{
- message = SMB1Message.GetSMB1Message(packet.Trailer);
+ if (!acceptSMB1)
+ {
+ state.LogToServer(Severity.Verbose, "Rejected SMB1 message");
+ state.ClientSocket.Close();
+ return;
+ }
+
+ SMB1Message message = null;
+ try
+ {
+ message = SMB1Message.GetSMB1Message(packet.Trailer);
+ }
+ catch (Exception ex)
+ {
+ state.LogToServer(Severity.Warning, "Invalid SMB1 message: " + ex.Message);
+ state.ClientSocket.Close();
+ return;
+ }
+ state.LogToServer(Severity.Verbose, "SMB1 message received: {0} requests, First request: {1}, Packet length: {2}", message.Commands.Count, message.Commands[0].CommandName.ToString(), packet.Length);
+ if (state.ServerDialect == SMBDialect.NotSet && m_enableSMB2)
+ {
+ // Check if the client supports SMB 2
+ List smb2Dialects = SMB2.NegotiateHelper.FindSMB2Dialects(message);
+ if (smb2Dialects.Count > 0)
+ {
+ SMB2Command response = SMB2.NegotiateHelper.GetNegotiateResponse(smb2Dialects, state, m_serverGuid);
+ TrySendResponse(state, response);
+ return;
+ }
+ }
+
+ if (m_enableSMB1)
+ {
+ ProcessSMB1Message(message, ref state);
+ }
+ else
+ {
+ // [MS-SMB2] 3.3.5.3.2 If the string is not present in the dialect list and the server does not implement SMB,
+ // the server MUST disconnect the connection [..] without sending a response.
+ state.LogToServer(Severity.Verbose, "Rejected SMB1 message");
+ state.ClientSocket.Close();
+ }
}
- catch (Exception ex)
+ else if (SMB2Header.IsValidSMB2Header(packet.Trailer))
{
- state.LogToServer(Severity.Warning, "Invalid SMB1 message: " + ex.Message);
+ if (!acceptSMB2)
+ {
+ state.LogToServer(Severity.Verbose, "Rejected SMB2 message");
+ state.ClientSocket.Close();
+ return;
+ }
+
+ List requestChain;
+ try
+ {
+ requestChain = SMB2Command.ReadRequestChain(packet.Trailer, 0);
+ }
+ catch (Exception ex)
+ {
+ state.LogToServer(Severity.Warning, "Invalid SMB2 request chain: " + ex.Message);
+ state.ClientSocket.Close();
+ return;
+ }
+ state.LogToServer(Severity.Verbose, "SMB2 request chain received: {0} requests, First request: {1}, Packet length: {2}", requestChain.Count, requestChain[0].CommandName.ToString(), packet.Length);
+ List responseChain = new List();
+ foreach (SMB2Command request in requestChain)
+ {
+ SMB2Command response = ProcessSMB2Command(request, ref state);
+ if (response != null)
+ {
+ UpdateSMB2Header(response, request);
+ responseChain.Add(response);
+ }
+ }
+ if (responseChain.Count > 0)
+ {
+ TrySendResponseChain(state, responseChain);
+ }
+ }
+ else
+ {
+ state.LogToServer(Severity.Warning, "Invalid SMB message");
state.ClientSocket.Close();
- return;
}
- state.LogToServer(Severity.Verbose, "Message Received: {0} Commands, First Command: {1}, Packet length: {2}", message.Commands.Count, message.Commands[0].CommandName.ToString(), packet.Length);
- ProcessSMB1Message(message, state);
}
else
{
@@ -225,7 +312,7 @@ namespace SMBLibrary.Server
}
}
- public static void TrySendPacket(SMB1ConnectionState state, SessionPacket response)
+ public static void TrySendPacket(ConnectionState state, SessionPacket response)
{
Socket clientSocket = state.ClientSocket;
try