diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 87a50f8..dd12fde 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -114,6 +114,7 @@ + diff --git a/SMBLibrary/Server/Helpers/NTFileSystemHelper.cs b/SMBLibrary/Server/Helpers/NTFileSystemHelper.cs new file mode 100644 index 0000000..ff61bc7 --- /dev/null +++ b/SMBLibrary/Server/Helpers/NTFileSystemHelper.cs @@ -0,0 +1,417 @@ +/* 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.Services; +using Utilities; + +namespace SMBLibrary.Server +{ + /// + /// Helper class to access the FileSystemShare / NamedPipeShare in an NT-like manner dictated by the SMB protocol + /// + public partial class NTFileSystemHelper + { + public const int BytesPerSector = 512; + public const int ClusterSize = 4096; + + public static NTStatus CreateFile(out FileSystemEntry entry, FileSystemShare share, string userName, string path, CreateDisposition createDisposition, CreateOptions createOptions, AccessMask desiredAccess, ConnectionState state) + { + bool hasWriteAccess = share.HasWriteAccess(userName); + IFileSystem fileSystem = share.FileSystem; + + bool forceDirectory = (createOptions & CreateOptions.FILE_DIRECTORY_FILE) > 0; + bool forceFile = (createOptions & CreateOptions.FILE_NON_DIRECTORY_FILE) > 0; + + if (forceDirectory & (createDisposition != CreateDisposition.FILE_CREATE && + createDisposition != CreateDisposition.FILE_OPEN && + createDisposition != CreateDisposition.FILE_OPEN_IF)) + { + entry = null; + return NTStatus.STATUS_INVALID_PARAMETER; + } + + // Windows will try to access named streams (alternate data streams) regardless of the FILE_NAMED_STREAMS flag, we need to prevent this behaviour. + if (path.Contains(":")) + { + // Windows Server 2003 will return STATUS_OBJECT_NAME_NOT_FOUND + entry = null; + return NTStatus.STATUS_NO_SUCH_FILE; + } + + entry = fileSystem.GetEntry(path); + if (createDisposition == CreateDisposition.FILE_OPEN) + { + if (entry == null) + { + return NTStatus.STATUS_OBJECT_PATH_NOT_FOUND; + } + + if (entry.IsDirectory && forceFile) + { + return NTStatus.STATUS_FILE_IS_A_DIRECTORY; + } + + if (!entry.IsDirectory && forceDirectory) + { + // Not sure if that's the correct response + return NTStatus.STATUS_OBJECT_NAME_COLLISION; + } + } + else if (createDisposition == CreateDisposition.FILE_CREATE) + { + if (entry != null) + { + // File already exists, fail the request + state.LogToServer(Severity.Debug, "NTCreate: File '{0}' already exist", path); + return NTStatus.STATUS_OBJECT_NAME_COLLISION; + } + + if (!hasWriteAccess) + { + return NTStatus.STATUS_ACCESS_DENIED; + } + + try + { + if (forceDirectory) + { + state.LogToServer(Severity.Information, "NTCreate: Creating directory '{0}'", path); + entry = fileSystem.CreateDirectory(path); + } + else + { + state.LogToServer(Severity.Information, "NTCreate: Creating file '{0}'", path); + entry = fileSystem.CreateFile(path); + } + } + catch (IOException ex) + { + ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex); + if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION) + { + state.LogToServer(Severity.Debug, "NTCreate: Sharing violation creating '{0}'", path); + return NTStatus.STATUS_SHARING_VIOLATION; + } + else + { + state.LogToServer(Severity.Debug, "NTCreate: Error creating '{0}'", path); + return NTStatus.STATUS_DATA_ERROR; + } + } + catch (UnauthorizedAccessException) + { + state.LogToServer(Severity.Debug, "NTCreate: Error creating '{0}', Access Denied", path); + return NTStatus.STATUS_ACCESS_DENIED; + } + } + else if (createDisposition == CreateDisposition.FILE_OPEN_IF || + createDisposition == CreateDisposition.FILE_OVERWRITE || + createDisposition == CreateDisposition.FILE_OVERWRITE_IF || + createDisposition == CreateDisposition.FILE_SUPERSEDE) + { + entry = fileSystem.GetEntry(path); + if (entry == null) + { + if (createDisposition == CreateDisposition.FILE_OVERWRITE) + { + return NTStatus.STATUS_OBJECT_PATH_NOT_FOUND; + } + + if (!hasWriteAccess) + { + return NTStatus.STATUS_ACCESS_DENIED; + } + + try + { + if (forceDirectory) + { + state.LogToServer(Severity.Information, "NTCreate: Creating directory '{0}'", path); + entry = fileSystem.CreateDirectory(path); + } + else + { + state.LogToServer(Severity.Information, "NTCreate: Creating file '{0}'", path); + entry = fileSystem.CreateFile(path); + } + } + catch (IOException ex) + { + ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex); + if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION) + { + return NTStatus.STATUS_SHARING_VIOLATION; + } + else + { + return NTStatus.STATUS_DATA_ERROR; + } + } + catch (UnauthorizedAccessException) + { + return NTStatus.STATUS_ACCESS_DENIED; + } + } + else + { + if (createDisposition == CreateDisposition.FILE_OVERWRITE || + createDisposition == CreateDisposition.FILE_OVERWRITE_IF || + createDisposition == CreateDisposition.FILE_SUPERSEDE) + { + if (!hasWriteAccess) + { + return NTStatus.STATUS_ACCESS_DENIED; + } + + // Truncate the file + try + { + Stream temp = fileSystem.OpenFile(path, FileMode.Truncate, FileAccess.ReadWrite, FileShare.ReadWrite); + temp.Close(); + } + catch (IOException ex) + { + ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex); + if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION) + { + return NTStatus.STATUS_SHARING_VIOLATION; + } + else + { + return NTStatus.STATUS_DATA_ERROR; + } + } + catch (UnauthorizedAccessException) + { + return NTStatus.STATUS_ACCESS_DENIED; + } + } + } + } + else + { + return NTStatus.STATUS_INVALID_PARAMETER; + } + + FileAccess fileAccess = ToFileAccess(desiredAccess.File); + if (!hasWriteAccess && (fileAccess == FileAccess.Write || fileAccess == FileAccess.ReadWrite)) + { + return NTStatus.STATUS_ACCESS_DENIED; + } + + return NTStatus.STATUS_SUCCESS; + } + + public static FileAccess ToFileAccess(FileAccessMask desiredAccess) + { + if ((desiredAccess & FileAccessMask.GENERIC_ALL) > 0 || + ((desiredAccess & FileAccessMask.FILE_READ_DATA) > 0 && (desiredAccess & FileAccessMask.FILE_WRITE_DATA) > 0) || + ((desiredAccess & FileAccessMask.FILE_READ_DATA) > 0 && (desiredAccess & FileAccessMask.FILE_APPEND_DATA) > 0)) + { + return FileAccess.ReadWrite; + } + else if ((desiredAccess & FileAccessMask.GENERIC_WRITE) > 0 || + (desiredAccess & FileAccessMask.FILE_WRITE_DATA) > 0 || + (desiredAccess & FileAccessMask.FILE_APPEND_DATA) > 0) + { + return FileAccess.Write; + } + else if ((desiredAccess & FileAccessMask.FILE_READ_DATA) > 0) + { + return FileAccess.Read; + } + else + { + return (FileAccess)0; + } + } + + public static FileShare ToFileShare(ShareAccess shareAccess) + { + if ((shareAccess & ShareAccess.FILE_SHARE_READ) > 0 && (shareAccess & ShareAccess.FILE_SHARE_WRITE) > 0) + { + return FileShare.ReadWrite; + } + else if ((shareAccess & ShareAccess.FILE_SHARE_WRITE) > 0) + { + return FileShare.Write; + } + else if ((shareAccess & ShareAccess.FILE_SHARE_READ) > 0) + { + return FileShare.Read; + } + else if ((shareAccess & ShareAccess.FILE_SHARE_DELETE) > 0) + { + return FileShare.Delete; + } + else + { + return FileShare.None; + } + } + + public static NTStatus ReadFile(out byte[] data, OpenFileObject openFile, long offset, int maxCount, ConnectionState state) + { + data = null; + string openFilePath = openFile.Path; + Stream stream = openFile.Stream; + if (stream is RPCPipeStream) + { + data = new byte[maxCount]; + int bytesRead = stream.Read(data, 0, maxCount); + if (bytesRead < maxCount) + { + // EOF, we must trim the response data array + data = ByteReader.ReadBytes(data, 0, bytesRead); + } + return NTStatus.STATUS_SUCCESS; + } + else // File + { + if (stream == null) + { + state.LogToServer(Severity.Debug, "ReadFile: Cannot read '{0}', Invalid Operation.", openFilePath); + return NTStatus.STATUS_ACCESS_DENIED; + } + + int bytesRead; + try + { + stream.Seek(offset, SeekOrigin.Begin); + data = new byte[maxCount]; + bytesRead = stream.Read(data, 0, maxCount); + } + catch (IOException ex) + { + ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex); + if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION) + { + // Returning STATUS_SHARING_VIOLATION is undocumented but apparently valid + state.LogToServer(Severity.Debug, "ReadFile: Cannot read '{0}'. Sharing Violation.", openFilePath); + return NTStatus.STATUS_SHARING_VIOLATION; + } + else + { + state.LogToServer(Severity.Debug, "ReadFile: Cannot read '{0}'. Data Error.", openFilePath); + return NTStatus.STATUS_DATA_ERROR; + } + } + catch (ArgumentOutOfRangeException) + { + state.LogToServer(Severity.Debug, "ReadFile: Cannot read '{0}'. Offset Out Of Range.", openFilePath); + return NTStatus.STATUS_DATA_ERROR; + } + catch (UnauthorizedAccessException) + { + state.LogToServer(Severity.Debug, "ReadFile: Cannot read '{0}', Access Denied.", openFilePath); + return NTStatus.STATUS_ACCESS_DENIED; + } + + if (bytesRead < maxCount) + { + // EOF, we must trim the response data array + data = ByteReader.ReadBytes(data, 0, bytesRead); + } + return NTStatus.STATUS_SUCCESS; + } + } + + public static NTStatus WriteFile(out int numberOfBytesWritten, OpenFileObject openFile, long offset, byte[] data, ConnectionState state) + { + numberOfBytesWritten = 0; + string openFilePath = openFile.Path; + Stream stream = openFile.Stream; + if (stream is RPCPipeStream) + { + stream.Write(data, 0, data.Length); + numberOfBytesWritten = data.Length; + return NTStatus.STATUS_SUCCESS; + } + else // File + { + if (stream == null) + { + state.LogToServer(Severity.Debug, "WriteFile: Cannot write '{0}'. Invalid Operation.", openFilePath); + return NTStatus.STATUS_ACCESS_DENIED; + } + + try + { + stream.Seek(offset, SeekOrigin.Begin); + stream.Write(data, 0, data.Length); + numberOfBytesWritten = data.Length; + return NTStatus.STATUS_SUCCESS; + } + catch (IOException ex) + { + ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex); + if (errorCode == (ushort)Win32Error.ERROR_DISK_FULL) + { + state.LogToServer(Severity.Debug, "WriteFile: Cannot write '{0}'. Disk Full.", openFilePath); + return NTStatus.STATUS_DISK_FULL; + } + else if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION) + { + state.LogToServer(Severity.Debug, "WriteFile: Cannot write '{0}'. Sharing Violation.", openFilePath); + // Returning STATUS_SHARING_VIOLATION is undocumented but apparently valid + return NTStatus.STATUS_SHARING_VIOLATION; + } + else + { + state.LogToServer(Severity.Debug, "WriteFile: Cannot write '{0}'. Data Error.", openFilePath); + return NTStatus.STATUS_DATA_ERROR; + } + } + catch (ArgumentOutOfRangeException) + { + state.LogToServer(Severity.Debug, "WriteFile: Cannot write '{0}'. Offset Out Of Range.", openFilePath); + return NTStatus.STATUS_DATA_ERROR; + } + catch (UnauthorizedAccessException) + { + state.LogToServer(Severity.Debug, "WriteFile: Cannot write '{0}'. Access Denied.", openFilePath); + // The user may have tried to write to a readonly file + return NTStatus.STATUS_ACCESS_DENIED; + } + } + } + + /// + /// Will return a virtual allocation size, assuming 4096 bytes per cluster + /// + public static ulong GetAllocationSize(ulong size) + { + return (ulong)Math.Ceiling((double)size / ClusterSize) * ClusterSize; + } + + public static string GetShortName(string fileName) + { + string fileNameWithoutExt = System.IO.Path.GetFileNameWithoutExtension(fileName); + string extension = System.IO.Path.GetExtension(fileName); + if (fileNameWithoutExt.Length > 8 || extension.Length > 4) + { + if (fileNameWithoutExt.Length > 8) + { + fileNameWithoutExt = fileNameWithoutExt.Substring(0, 8); + } + + if (extension.Length > 4) + { + extension = extension.Substring(0, 4); + } + + return fileNameWithoutExt + extension; + } + else + { + return fileName; + } + } + } +} diff --git a/SMBLibrary/Server/SMB1/InfoHelper.cs b/SMBLibrary/Server/SMB1/InfoHelper.cs index aa91b5b..bdafb33 100644 --- a/SMBLibrary/Server/SMB1/InfoHelper.cs +++ b/SMBLibrary/Server/SMB1/InfoHelper.cs @@ -14,9 +14,6 @@ namespace SMBLibrary.Server.SMB1 { public class InfoHelper { - public const int BytesPerSector = 512; - public const int ClusterSize = 4096; - internal static FindInformation FromFileSystemEntry(FileSystemEntry entry, FindInformationLevel informationLevel, bool isUnicode, bool returnResumeKeys) { switch (informationLevel) @@ -28,7 +25,7 @@ namespace SMBLibrary.Server.SMB1 result.LastAccessDateTime = entry.LastAccessTime; result.LastWriteDateTime = entry.LastWriteTime; result.FileDataSize = (uint)Math.Min(entry.Size, UInt32.MaxValue); - result.AllocationSize = (uint)Math.Min(GetAllocationSize(entry.Size), UInt32.MaxValue); + result.AllocationSize = (uint)Math.Min(NTFileSystemHelper.GetAllocationSize(entry.Size), UInt32.MaxValue); result.Attributes = GetFileAttributes(entry); result.FileName = entry.Name; return result; @@ -40,7 +37,7 @@ namespace SMBLibrary.Server.SMB1 result.LastAccessDateTime = entry.LastAccessTime; result.LastWriteDateTime = entry.LastWriteTime; result.FileDataSize = (uint)Math.Min(entry.Size, UInt32.MaxValue); - result.AllocationSize = (uint)Math.Min(GetAllocationSize(entry.Size), UInt32.MaxValue); + result.AllocationSize = (uint)Math.Min(NTFileSystemHelper.GetAllocationSize(entry.Size), UInt32.MaxValue); result.Attributes = GetFileAttributes(entry); result.EASize = 0; result.FileName = entry.Name; @@ -53,7 +50,7 @@ namespace SMBLibrary.Server.SMB1 result.LastAccessDateTime = entry.LastAccessTime; result.LastWriteDateTime = entry.LastWriteTime; result.FileDataSize = (uint)Math.Min(entry.Size, UInt32.MaxValue); - result.AllocationSize = (uint)Math.Min(GetAllocationSize(entry.Size), UInt32.MaxValue); + result.AllocationSize = (uint)Math.Min(NTFileSystemHelper.GetAllocationSize(entry.Size), UInt32.MaxValue); result.Attributes = GetFileAttributes(entry); result.ExtendedAttributeList = new FullExtendedAttributeList(); return result; @@ -66,7 +63,7 @@ namespace SMBLibrary.Server.SMB1 result.LastWriteTime = entry.LastWriteTime; result.LastAttrChangeTime = entry.LastWriteTime; result.EndOfFile = entry.Size; - result.AllocationSize = GetAllocationSize(entry.Size); + result.AllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size); result.ExtFileAttributes = GetExtendedFileAttributes(entry); result.FileName = entry.Name; return result; @@ -79,7 +76,7 @@ namespace SMBLibrary.Server.SMB1 result.LastWriteTime = entry.LastWriteTime; result.LastAttrChangeTime = entry.LastWriteTime; result.EndOfFile = entry.Size; - result.AllocationSize = GetAllocationSize(entry.Size); + result.AllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size); result.ExtFileAttributes = GetExtendedFileAttributes(entry); result.FileName = entry.Name; return result; @@ -98,9 +95,9 @@ namespace SMBLibrary.Server.SMB1 result.LastWriteTime = entry.LastWriteTime; result.LastChangeTime = entry.LastWriteTime; result.EndOfFile = entry.Size; - result.AllocationSize = GetAllocationSize(entry.Size); + result.AllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size); result.ExtFileAttributes = GetExtendedFileAttributes(entry); - result.ShortName = GetShortName(entry.Name); + result.ShortName = NTFileSystemHelper.GetShortName(entry.Name); result.FileName = entry.Name; return result; } @@ -122,7 +119,7 @@ namespace SMBLibrary.Server.SMB1 result.LastAccessDateTime = entry.LastAccessTime; result.LastWriteDateTime = entry.LastWriteTime; result.FileDataSize = (uint)Math.Min(entry.Size, UInt32.MaxValue); - result.AllocationSize = (uint)Math.Min(GetAllocationSize(entry.Size), UInt32.MaxValue); + result.AllocationSize = (uint)Math.Min(NTFileSystemHelper.GetAllocationSize(entry.Size), UInt32.MaxValue); return result; } case QueryInformationLevel.SMB_INFO_QUERY_EA_SIZE: @@ -132,7 +129,7 @@ namespace SMBLibrary.Server.SMB1 result.LastAccessDateTime = entry.LastAccessTime; result.LastWriteDateTime = entry.LastWriteTime; result.FileDataSize = (uint)Math.Min(entry.Size, UInt32.MaxValue); - result.AllocationSize = (uint)Math.Min(GetAllocationSize(entry.Size), UInt32.MaxValue); + result.AllocationSize = (uint)Math.Min(NTFileSystemHelper.GetAllocationSize(entry.Size), UInt32.MaxValue); result.Attributes = GetFileAttributes(entry); result.EASize = 0; return result; @@ -162,7 +159,7 @@ namespace SMBLibrary.Server.SMB1 case QueryInformationLevel.SMB_QUERY_FILE_STANDARD_INFO: { QueryFileStandardInfo result = new QueryFileStandardInfo(); - result.AllocationSize = GetAllocationSize(entry.Size); + result.AllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size); result.EndOfFile = entry.Size; result.DeletePending = deletePending; result.Directory = entry.IsDirectory; @@ -188,7 +185,7 @@ namespace SMBLibrary.Server.SMB1 result.LastWriteDateTime = entry.LastWriteTime; result.ExtFileAttributes = GetExtendedFileAttributes(entry); result.LastChangeTime = entry.LastWriteTime; - result.AllocationSize = GetAllocationSize(entry.Size); + result.AllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size); result.EndOfFile = entry.Size; result.DeletePending = deletePending; result.Directory = entry.IsDirectory; @@ -199,14 +196,14 @@ namespace SMBLibrary.Server.SMB1 case QueryInformationLevel.SMB_QUERY_FILE_ALT_NAME_INFO: { QueryFileAltNameInfo result = new QueryFileAltNameInfo(); - result.FileName = GetShortName(entry.Name); + result.FileName = NTFileSystemHelper.GetShortName(entry.Name); return result; } case QueryInformationLevel.SMB_QUERY_FILE_STREAM_INFO: { QueryFileStreamInfo result = new QueryFileStreamInfo(); result.StreamSize = entry.Size; - result.StreamAllocationSize = GetAllocationSize(entry.Size); + result.StreamAllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size); result.StreamName = "::$DATA"; return result; } @@ -231,10 +228,10 @@ namespace SMBLibrary.Server.SMB1 { QueryFSInfoAllocation result = new QueryFSInfoAllocation(); result.FileSystemID = 0; - result.SectorUnit = ClusterSize / BytesPerSector; - result.UnitsTotal = (uint)Math.Min(fileSystem.Size / ClusterSize, UInt32.MaxValue); - result.UnitsAvailable = (uint)Math.Min(fileSystem.FreeSpace / ClusterSize, UInt32.MaxValue); - result.Sector = BytesPerSector; + result.SectorUnit = NTFileSystemHelper.ClusterSize / NTFileSystemHelper.BytesPerSector; + result.UnitsTotal = (uint)Math.Min(fileSystem.Size / NTFileSystemHelper.ClusterSize, UInt32.MaxValue); + result.UnitsAvailable = (uint)Math.Min(fileSystem.FreeSpace / NTFileSystemHelper.ClusterSize, UInt32.MaxValue); + result.Sector = NTFileSystemHelper.BytesPerSector; return result; } case QueryFSInformationLevel.SMB_INFO_VOLUME: @@ -253,10 +250,10 @@ namespace SMBLibrary.Server.SMB1 case QueryFSInformationLevel.SMB_QUERY_FS_SIZE_INFO: { QueryFSSizeInfo result = new QueryFSSizeInfo(); - result.TotalAllocationUnits = (ulong)(fileSystem.Size / ClusterSize); - result.TotalFreeAllocationUnits = (ulong)(fileSystem.FreeSpace / ClusterSize); - result.BytesPerSector = BytesPerSector; - result.SectorsPerAllocationUnit = ClusterSize / BytesPerSector; + result.TotalAllocationUnits = (ulong)(fileSystem.Size / NTFileSystemHelper.ClusterSize); + result.TotalFreeAllocationUnits = (ulong)(fileSystem.FreeSpace / NTFileSystemHelper.ClusterSize); + result.BytesPerSector = NTFileSystemHelper.BytesPerSector; + result.SectorsPerAllocationUnit = NTFileSystemHelper.ClusterSize / NTFileSystemHelper.BytesPerSector; return result; } case QueryFSInformationLevel.SMB_QUERY_FS_DEVICE_INFO: @@ -281,39 +278,6 @@ namespace SMBLibrary.Server.SMB1 } } - /// - /// Will return a virtual allocation size, assuming 4096 bytes per cluster - /// - public static ulong GetAllocationSize(ulong size) - { - return (ulong)Math.Ceiling((double)size / ClusterSize) * ClusterSize; - } - - private static string GetShortName(string filename) - { - string fileNameWithoutExt = System.IO.Path.GetFileNameWithoutExtension(filename); - string extension = System.IO.Path.GetExtension(filename); - if (fileNameWithoutExt.Length > 8 || extension.Length > 4) - { - if (fileNameWithoutExt.Length > 8) - { - fileNameWithoutExt = fileNameWithoutExt.Substring(0, 8); - } - - if (extension.Length > 4) - { - extension = extension.Substring(0, 4); - } - - return fileNameWithoutExt + extension; - } - else - { - return filename; - } - } - - public static SMBFileAttributes GetFileAttributes(FileSystemEntry entry) { SMBFileAttributes attributes = SMBFileAttributes.Normal; @@ -339,7 +303,7 @@ namespace SMBLibrary.Server.SMB1 public static ExtendedFileAttributes GetExtendedFileAttributes(FileSystemEntry entry) { - ExtendedFileAttributes attributes = (ExtendedFileAttributes)0; + ExtendedFileAttributes attributes = 0; if (entry.IsHidden) { attributes |= ExtendedFileAttributes.Hidden; @@ -357,7 +321,7 @@ namespace SMBLibrary.Server.SMB1 attributes |= ExtendedFileAttributes.Directory; } - if (attributes == (ExtendedFileAttributes)0) + if ((uint)attributes == 0) { attributes = ExtendedFileAttributes.Normal; } diff --git a/SMBLibrary/Server/SMB1/NTCreateHelper.cs b/SMBLibrary/Server/SMB1/NTCreateHelper.cs index 83fd6f8..5036546 100644 --- a/SMBLibrary/Server/SMB1/NTCreateHelper.cs +++ b/SMBLibrary/Server/SMB1/NTCreateHelper.cs @@ -50,7 +50,7 @@ namespace SMBLibrary.Server.SMB1 FileSystemShare fileSystemShare = (FileSystemShare)share; string userName = session.UserName; FileSystemEntry entry; - NTStatus createStatus = CreateFile(out entry, fileSystemShare, userName, path, request.CreateDisposition, request.CreateOptions, request.DesiredAccess, state); + NTStatus createStatus = NTFileSystemHelper.CreateFile(out entry, fileSystemShare, userName, path, request.CreateDisposition, request.CreateOptions, request.DesiredAccess, state); if (createStatus != NTStatus.STATUS_SUCCESS) { header.Status = createStatus; @@ -58,8 +58,8 @@ namespace SMBLibrary.Server.SMB1 } IFileSystem fileSystem = fileSystemShare.FileSystem; - FileAccess fileAccess = ToFileAccess(request.DesiredAccess); - FileShare fileShare = ToFileShare(request.ShareAccess); + FileAccess fileAccess = NTFileSystemHelper.ToFileAccess(request.DesiredAccess); + FileShare fileShare = NTFileSystemHelper.ToFileShare(request.ShareAccess); Stream stream; bool deleteOnClose = false; @@ -137,242 +137,6 @@ namespace SMBLibrary.Server.SMB1 } } - public static NTStatus CreateFile(out FileSystemEntry entry, FileSystemShare share, string userName, string path, CreateDisposition createDisposition, CreateOptions createOptions, AccessMask desiredAccess, ConnectionState state) - { - bool hasWriteAccess = share.HasWriteAccess(userName); - IFileSystem fileSystem = share.FileSystem; - - bool forceDirectory = (createOptions & CreateOptions.FILE_DIRECTORY_FILE) > 0; - bool forceFile = (createOptions & CreateOptions.FILE_NON_DIRECTORY_FILE) > 0; - - if (forceDirectory & (createDisposition != CreateDisposition.FILE_CREATE && - createDisposition != CreateDisposition.FILE_OPEN && - createDisposition != CreateDisposition.FILE_OPEN_IF)) - { - entry = null; - return NTStatus.STATUS_INVALID_PARAMETER; - } - - // Windows will try to access named streams (alternate data streams) regardless of the FILE_NAMED_STREAMS flag, we need to prevent this behaviour. - if (path.Contains(":")) - { - // Windows Server 2003 will return STATUS_OBJECT_NAME_NOT_FOUND - entry = null; - return NTStatus.STATUS_NO_SUCH_FILE; - } - - entry = fileSystem.GetEntry(path); - if (createDisposition == CreateDisposition.FILE_OPEN) - { - if (entry == null) - { - return NTStatus.STATUS_OBJECT_PATH_NOT_FOUND; - } - - if (entry.IsDirectory && forceFile) - { - return NTStatus.STATUS_FILE_IS_A_DIRECTORY; - } - - if (!entry.IsDirectory && forceDirectory) - { - // Not sure if that's the correct response - return NTStatus.STATUS_OBJECT_NAME_COLLISION; - } - } - else if (createDisposition == CreateDisposition.FILE_CREATE) - { - if (entry != null) - { - // File already exists, fail the request - state.LogToServer(Severity.Debug, "NTCreate: File '{0}' already exist", path); - return NTStatus.STATUS_OBJECT_NAME_COLLISION; - } - - if (!hasWriteAccess) - { - return NTStatus.STATUS_ACCESS_DENIED; - } - - try - { - if (forceDirectory) - { - state.LogToServer(Severity.Information, "NTCreate: Creating directory '{0}'", path); - entry = fileSystem.CreateDirectory(path); - } - else - { - state.LogToServer(Severity.Information, "NTCreate: Creating file '{0}'", path); - entry = fileSystem.CreateFile(path); - } - } - catch (IOException ex) - { - ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex); - if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION) - { - state.LogToServer(Severity.Debug, "NTCreate: Sharing violation creating '{0}'", path); - return NTStatus.STATUS_SHARING_VIOLATION; - } - else - { - state.LogToServer(Severity.Debug, "NTCreate: Error creating '{0}'", path); - return NTStatus.STATUS_DATA_ERROR; - } - } - catch (UnauthorizedAccessException) - { - state.LogToServer(Severity.Debug, "NTCreate: Error creating '{0}', Access Denied", path); - return NTStatus.STATUS_ACCESS_DENIED; - } - } - else if (createDisposition == CreateDisposition.FILE_OPEN_IF || - createDisposition == CreateDisposition.FILE_OVERWRITE || - createDisposition == CreateDisposition.FILE_OVERWRITE_IF || - createDisposition == CreateDisposition.FILE_SUPERSEDE) - { - entry = fileSystem.GetEntry(path); - if (entry == null) - { - if (createDisposition == CreateDisposition.FILE_OVERWRITE) - { - return NTStatus.STATUS_OBJECT_PATH_NOT_FOUND; - } - - if (!hasWriteAccess) - { - return NTStatus.STATUS_ACCESS_DENIED; - } - - try - { - if (forceDirectory) - { - state.LogToServer(Severity.Information, "NTCreate: Creating directory '{0}'", path); - entry = fileSystem.CreateDirectory(path); - } - else - { - state.LogToServer(Severity.Information, "NTCreate: Creating file '{0}'", path); - entry = fileSystem.CreateFile(path); - } - } - catch (IOException ex) - { - ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex); - if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION) - { - return NTStatus.STATUS_SHARING_VIOLATION; - } - else - { - return NTStatus.STATUS_DATA_ERROR; - } - } - catch (UnauthorizedAccessException) - { - return NTStatus.STATUS_ACCESS_DENIED; - } - } - else - { - if (createDisposition == CreateDisposition.FILE_OVERWRITE || - createDisposition == CreateDisposition.FILE_OVERWRITE_IF || - createDisposition == CreateDisposition.FILE_SUPERSEDE) - { - if (!hasWriteAccess) - { - return NTStatus.STATUS_ACCESS_DENIED; - } - - // Truncate the file - try - { - Stream temp = fileSystem.OpenFile(path, FileMode.Truncate, FileAccess.ReadWrite, FileShare.ReadWrite); - temp.Close(); - } - catch (IOException ex) - { - ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex); - if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION) - { - return NTStatus.STATUS_SHARING_VIOLATION; - } - else - { - return NTStatus.STATUS_DATA_ERROR; - } - } - catch (UnauthorizedAccessException) - { - return NTStatus.STATUS_ACCESS_DENIED; - } - } - } - } - else - { - return NTStatus.STATUS_INVALID_PARAMETER; - } - - FileAccess fileAccess = ToFileAccess(desiredAccess.File); - if (!hasWriteAccess && (fileAccess == FileAccess.Write || fileAccess == FileAccess.ReadWrite)) - { - return NTStatus.STATUS_ACCESS_DENIED; - } - - return NTStatus.STATUS_SUCCESS; - } - - public static FileAccess ToFileAccess(FileAccessMask desiredAccess) - { - if ((desiredAccess & FileAccessMask.GENERIC_ALL) > 0 || - ((desiredAccess & FileAccessMask.FILE_READ_DATA) > 0 && (desiredAccess & FileAccessMask.FILE_WRITE_DATA) > 0) || - ((desiredAccess & FileAccessMask.FILE_READ_DATA) > 0 && (desiredAccess & FileAccessMask.FILE_APPEND_DATA) > 0)) - { - return FileAccess.ReadWrite; - } - else if ((desiredAccess & FileAccessMask.GENERIC_WRITE) > 0 || - (desiredAccess & FileAccessMask.FILE_WRITE_DATA) > 0 || - (desiredAccess & FileAccessMask.FILE_APPEND_DATA) > 0) - { - return FileAccess.Write; - } - else if ((desiredAccess & FileAccessMask.FILE_READ_DATA) > 0) - { - return FileAccess.Read; - } - else - { - return (FileAccess)0; - } - } - - public static FileShare ToFileShare(ShareAccess shareAccess) - { - if ((shareAccess & ShareAccess.FILE_SHARE_READ) > 0 && (shareAccess & ShareAccess.FILE_SHARE_WRITE) > 0) - { - return FileShare.ReadWrite; - } - else if ((shareAccess & ShareAccess.FILE_SHARE_WRITE) > 0) - { - return FileShare.Write; - } - else if ((shareAccess & ShareAccess.FILE_SHARE_READ) > 0) - { - return FileShare.Read; - } - else if ((shareAccess & ShareAccess.FILE_SHARE_DELETE) > 0) - { - return FileShare.Delete; - } - else - { - return FileShare.None; - } - } - private static NTCreateAndXResponse CreateResponseForNamedPipe(ushort fileID) { NTCreateAndXResponse response = new NTCreateAndXResponse(); @@ -424,7 +188,7 @@ namespace SMBLibrary.Server.SMB1 } response.FID = fileID; response.CreateDisposition = CreateDisposition.FILE_OPEN; - response.AllocationSize = InfoHelper.GetAllocationSize(entry.Size); + response.AllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size); response.EndOfFile = entry.Size; response.CreateTime = entry.CreationTime; response.LastAccessTime = entry.LastAccessTime; @@ -452,7 +216,7 @@ namespace SMBLibrary.Server.SMB1 response.LastWriteTime = entry.LastWriteTime; response.LastChangeTime = entry.LastWriteTime; response.CreateDisposition = CreateDisposition.FILE_OPEN; - response.AllocationSize = InfoHelper.GetAllocationSize(entry.Size); + response.AllocationSize = NTFileSystemHelper.GetAllocationSize(entry.Size); response.EndOfFile = entry.Size; response.ResourceType = ResourceType.FileTypeDisk; response.FileStatus = FileStatus.NO_EAS | FileStatus.NO_SUBSTREAMS | FileStatus.NO_REPARSETAG; diff --git a/SMBLibrary/Server/SMB1/ReadWriteResponseHelper.cs b/SMBLibrary/Server/SMB1/ReadWriteResponseHelper.cs index 2c8f43b..3b221fc 100644 --- a/SMBLibrary/Server/SMB1/ReadWriteResponseHelper.cs +++ b/SMBLibrary/Server/SMB1/ReadWriteResponseHelper.cs @@ -27,7 +27,7 @@ namespace SMBLibrary.Server.SMB1 return null; } byte[] data; - header.Status = ReadFile(out data, openFile, request.ReadOffsetInBytes, request.CountOfBytesToRead, state); + header.Status = NTFileSystemHelper.ReadFile(out data, openFile, request.ReadOffsetInBytes, request.CountOfBytesToRead, state); if (header.Status != NTStatus.STATUS_SUCCESS) { return new ErrorResponse(request.CommandName); @@ -54,7 +54,7 @@ namespace SMBLibrary.Server.SMB1 maxCount = request.MaxCountLarge; } byte[] data; - header.Status = ReadFile(out data, openFile, (long)request.Offset, (int)maxCount, state); + header.Status = NTFileSystemHelper.ReadFile(out data, openFile, (long)request.Offset, (int)maxCount, state); if (header.Status != NTStatus.STATUS_SUCCESS) { return new ErrorResponse(request.CommandName); @@ -70,72 +70,6 @@ namespace SMBLibrary.Server.SMB1 return response; } - public static NTStatus ReadFile(out byte[] data, OpenFileObject openFile, long offset, int maxCount, ConnectionState state) - { - data = null; - string openFilePath = openFile.Path; - Stream stream = openFile.Stream; - if (stream is RPCPipeStream) - { - data = new byte[maxCount]; - int bytesRead = stream.Read(data, 0, maxCount); - if (bytesRead < maxCount) - { - // EOF, we must trim the response data array - data = ByteReader.ReadBytes(data, 0, bytesRead); - } - return NTStatus.STATUS_SUCCESS; - } - else // File - { - if (stream == null) - { - state.LogToServer(Severity.Debug, "ReadFile: Cannot read '{0}', Invalid Operation.", openFilePath); - return NTStatus.STATUS_ACCESS_DENIED; - } - - int bytesRead; - try - { - stream.Seek(offset, SeekOrigin.Begin); - data = new byte[maxCount]; - bytesRead = stream.Read(data, 0, maxCount); - } - catch (IOException ex) - { - ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex); - if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION) - { - // Returning STATUS_SHARING_VIOLATION is undocumented but apparently valid - state.LogToServer(Severity.Debug, "ReadFile: Cannot read '{0}'. Sharing Violation.", openFilePath); - return NTStatus.STATUS_SHARING_VIOLATION; - } - else - { - state.LogToServer(Severity.Debug, "ReadFile: Cannot read '{0}'. Data Error.", openFilePath); - return NTStatus.STATUS_DATA_ERROR; - } - } - catch (ArgumentOutOfRangeException) - { - state.LogToServer(Severity.Debug, "ReadFile: Cannot read '{0}'. Offset Out Of Range.", openFilePath); - return NTStatus.STATUS_DATA_ERROR; - } - catch (UnauthorizedAccessException) - { - state.LogToServer(Severity.Debug, "ReadFile: Cannot read '{0}', Access Denied.", openFilePath); - return NTStatus.STATUS_ACCESS_DENIED; - } - - if (bytesRead < maxCount) - { - // EOF, we must trim the response data array - data = ByteReader.ReadBytes(data, 0, bytesRead); - } - return NTStatus.STATUS_SUCCESS; - } - } - internal static SMB1Command GetWriteResponse(SMB1Header header, WriteRequest request, ISMBShare share, SMB1ConnectionState state) { SMB1Session session = state.GetSession(header.UID); @@ -146,7 +80,7 @@ namespace SMBLibrary.Server.SMB1 return new ErrorResponse(request.CommandName); } int numberOfBytesWritten; - header.Status = WriteFile(out numberOfBytesWritten, openFile, request.WriteOffsetInBytes, request.Data, state); + header.Status = NTFileSystemHelper.WriteFile(out numberOfBytesWritten, openFile, request.WriteOffsetInBytes, request.Data, state); if (header.Status != NTStatus.STATUS_SUCCESS) { return new ErrorResponse(request.CommandName); @@ -166,7 +100,7 @@ namespace SMBLibrary.Server.SMB1 return new ErrorResponse(request.CommandName); } int numberOfBytesWritten; - header.Status = WriteFile(out numberOfBytesWritten, openFile, (long)request.Offset, request.Data, state); + header.Status = NTFileSystemHelper.WriteFile(out numberOfBytesWritten, openFile, (long)request.Offset, request.Data, state); if (header.Status != NTStatus.STATUS_SUCCESS) { return new ErrorResponse(request.CommandName); @@ -180,65 +114,5 @@ namespace SMBLibrary.Server.SMB1 } return response; } - - public static NTStatus WriteFile(out int numberOfBytesWritten, OpenFileObject openFile, long offset, byte[] data, ConnectionState state) - { - numberOfBytesWritten = 0; - string openFilePath = openFile.Path; - Stream stream = openFile.Stream; - if (stream is RPCPipeStream) - { - stream.Write(data, 0, data.Length); - numberOfBytesWritten = data.Length; - return NTStatus.STATUS_SUCCESS; - } - else // File - { - if (stream == null) - { - state.LogToServer(Severity.Debug, "WriteFile: Cannot write '{0}'. Invalid Operation.", openFilePath); - return NTStatus.STATUS_ACCESS_DENIED; - } - - try - { - stream.Seek(offset, SeekOrigin.Begin); - stream.Write(data, 0, data.Length); - numberOfBytesWritten = data.Length; - return NTStatus.STATUS_SUCCESS; - } - catch (IOException ex) - { - ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex); - if (errorCode == (ushort)Win32Error.ERROR_DISK_FULL) - { - state.LogToServer(Severity.Debug, "WriteFile: Cannot write '{0}'. Disk Full.", openFilePath); - return NTStatus.STATUS_DISK_FULL; - } - else if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION) - { - state.LogToServer(Severity.Debug, "WriteFile: Cannot write '{0}'. Sharing Violation.", openFilePath); - // Returning STATUS_SHARING_VIOLATION is undocumented but apparently valid - return NTStatus.STATUS_SHARING_VIOLATION; - } - else - { - state.LogToServer(Severity.Debug, "WriteFile: Cannot write '{0}'. Data Error.", openFilePath); - return NTStatus.STATUS_DATA_ERROR; - } - } - catch (ArgumentOutOfRangeException) - { - state.LogToServer(Severity.Debug, "WriteFile: Cannot write '{0}'. Offset Out Of Range.", openFilePath); - return NTStatus.STATUS_DATA_ERROR; - } - catch (UnauthorizedAccessException) - { - state.LogToServer(Severity.Debug, "WriteFile: Cannot write '{0}'. Access Denied.", openFilePath); - // The user may have tried to write to a readonly file - return NTStatus.STATUS_ACCESS_DENIED; - } - } - } } }