Browse Source

Added NTFileSystemHelper class

Helper class to access the FileSystemShare / NamedPipeShare in an
NT-like manner dictated by the SMB protocol
Tal Aloni 8 years ago
parent
commit
bac24a8b0a

+ 1 - 0
SMBLibrary/SMBLibrary.csproj

@@ -114,6 +114,7 @@
     <Compile Include="Server\Exceptions\InvalidRequestException.cs" />
     <Compile Include="Server\Exceptions\UnsupportedInformationLevelException.cs" />
     <Compile Include="Server\Helpers\IOExceptionHelper.cs" />
+    <Compile Include="Server\Helpers\NTFileSystemHelper.cs" />
     <Compile Include="Server\Helpers\ServerPathUtils.cs" />
     <Compile Include="Server\IndependentUserCollection.cs" />
     <Compile Include="Server\INTLMAuthenticationProvider.cs" />

+ 417 - 0
SMBLibrary/Server/Helpers/NTFileSystemHelper.cs

@@ -0,0 +1,417 @@
+/* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+ * 
+ * You can redistribute this program and/or modify it under the terms of
+ * the GNU Lesser Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ */
+using System;
+using System.Collections.Generic;
+using System.IO;
+using SMBLibrary.Services;
+using Utilities;
+
+namespace SMBLibrary.Server
+{
+    /// <summary>
+    /// Helper class to access the FileSystemShare / NamedPipeShare in an NT-like manner dictated by the SMB protocol
+    /// </summary>
+    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;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Will return a virtual allocation size, assuming 4096 bytes per cluster
+        /// </summary>
+        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;
+            }
+        }
+    }
+}

+ 23 - 59
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
             }
         }
 
-        /// <summary>
-        /// Will return a virtual allocation size, assuming 4096 bytes per cluster
-        /// </summary>
-        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;
             }

+ 5 - 241
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;

+ 4 - 130
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;
-                }
-            }
-        }
     }
 }