mirror of
https://github.com/TalAloni/SMBLibrary.git
synced 2025-08-12 02:19:17 +02:00
434 lines
21 KiB
C#
434 lines
21 KiB
C#
/* 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 System.Text;
|
|
using SMBLibrary.SMB1;
|
|
using Utilities;
|
|
|
|
namespace SMBLibrary.Server.SMB1
|
|
{
|
|
public class Transaction2SubcommandHelper
|
|
{
|
|
// Windows servers will return "." and ".." when enumerating directory files, Windows clients do not require it.
|
|
// It seems that Ubuntu 10.04.4 and 13.10 expect at least one entry in the response (so empty directory listing cause a problem when omitting both).
|
|
public const bool IncludeCurrentDirectoryInResults = true;
|
|
public const bool IncludeParentDirectoryInResults = true;
|
|
|
|
internal static Transaction2FindFirst2Response GetSubcommandResponse(SMB1Header header, Transaction2FindFirst2Request subcommand, FileSystemShare share, SMB1ConnectionState state)
|
|
{
|
|
SMB1Session session = state.GetSession(header.UID);
|
|
IFileSystem fileSystem = share.FileSystem;
|
|
string path = subcommand.FileName;
|
|
// '\Directory' - Get the directory info
|
|
// '\Directory\*' - List the directory files
|
|
// '\Directory\s*' - List the directory files starting with s (cmd.exe will use this syntax when entering 's' and hitting tab for autocomplete)
|
|
// '\Directory\<.inf' (Update driver will use this syntax)
|
|
// '\Directory\exefile"*' (cmd.exe will use this syntax when entering an exe without its extension, explorer will use this opening a directory from the run menu)
|
|
bool isDirectoryEnumeration = false;
|
|
string searchPattern = String.Empty;
|
|
if (path.Contains("*") || path.Contains("<"))
|
|
{
|
|
isDirectoryEnumeration = true;
|
|
int separatorIndex = path.LastIndexOf('\\');
|
|
searchPattern = path.Substring(separatorIndex + 1);
|
|
path = path.Substring(0, separatorIndex + 1);
|
|
}
|
|
bool exactNameWithoutExtension = searchPattern.Contains("\"");
|
|
|
|
FileSystemEntry entry = fileSystem.GetEntry(path);
|
|
if (entry == null)
|
|
{
|
|
header.Status = NTStatus.STATUS_NO_SUCH_FILE;
|
|
return null;
|
|
}
|
|
|
|
List<FileSystemEntry> entries;
|
|
if (isDirectoryEnumeration)
|
|
{
|
|
try
|
|
{
|
|
entries = fileSystem.ListEntriesInDirectory(path);
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
header.Status = NTStatus.STATUS_ACCESS_DENIED;
|
|
return null;
|
|
}
|
|
|
|
if (searchPattern != String.Empty)
|
|
{
|
|
entries = GetFiltered(entries, searchPattern);
|
|
}
|
|
|
|
if (!exactNameWithoutExtension)
|
|
{
|
|
if (IncludeParentDirectoryInResults)
|
|
{
|
|
entries.Insert(0, fileSystem.GetEntry(FileSystem.GetParentDirectory(path)));
|
|
entries[0].Name = "..";
|
|
}
|
|
if (IncludeCurrentDirectoryInResults)
|
|
{
|
|
entries.Insert(0, fileSystem.GetEntry(path));
|
|
entries[0].Name = ".";
|
|
}
|
|
}
|
|
|
|
// If no matching entries are found, the server SHOULD fail the request with STATUS_NO_SUCH_FILE.
|
|
if (entries.Count == 0)
|
|
{
|
|
header.Status = NTStatus.STATUS_NO_SUCH_FILE;
|
|
return null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
entries = new List<FileSystemEntry>();
|
|
entries.Add(entry);
|
|
}
|
|
|
|
bool returnResumeKeys = (subcommand.Flags & FindFlags.SMB_FIND_RETURN_RESUME_KEYS) > 0;
|
|
int entriesToReturn = Math.Min(subcommand.SearchCount, entries.Count);
|
|
// We ignore SearchAttributes
|
|
FindInformationList findInformationList = new FindInformationList();
|
|
for (int index = 0; index < entriesToReturn; index++)
|
|
{
|
|
FindInformation infoEntry = InfoHelper.FromFileSystemEntry(entries[index], subcommand.InformationLevel, header.UnicodeFlag, returnResumeKeys);
|
|
findInformationList.Add(infoEntry);
|
|
if (findInformationList.GetLength(header.UnicodeFlag) > state.GetMaxDataCount(header.PID))
|
|
{
|
|
findInformationList.RemoveAt(findInformationList.Count - 1);
|
|
break;
|
|
}
|
|
}
|
|
int returnCount = findInformationList.Count;
|
|
Transaction2FindFirst2Response response = new Transaction2FindFirst2Response();
|
|
response.SetFindInformationList(findInformationList, header.UnicodeFlag);
|
|
response.EndOfSearch = (returnCount == entries.Count);
|
|
bool closeAtEndOfSearch = (subcommand.Flags & FindFlags.SMB_FIND_CLOSE_AT_EOS) > 0;
|
|
bool closeAfterRequest = (subcommand.Flags & FindFlags.SMB_FIND_CLOSE_AFTER_REQUEST) > 0;
|
|
// If [..] the search fit within a single response and SMB_FIND_CLOSE_AT_EOS is set in the Flags field,
|
|
// or if SMB_FIND_CLOSE_AFTER_REQUEST is set in the request,
|
|
// the server SHOULD return a SID field value of zero.
|
|
// This indicates that the search has been closed and is no longer active on the server.
|
|
if ((response.EndOfSearch && closeAtEndOfSearch) || closeAfterRequest)
|
|
{
|
|
response.SID = 0;
|
|
}
|
|
else
|
|
{
|
|
ushort? searchHandle = session.AddOpenSearch(entries, returnCount);
|
|
if (!searchHandle.HasValue)
|
|
{
|
|
header.Status = NTStatus.STATUS_OS2_NO_MORE_SIDS;
|
|
return null;
|
|
}
|
|
response.SID = searchHandle.Value;
|
|
}
|
|
return response;
|
|
}
|
|
|
|
// [MS-FSA] 2.1.4.4
|
|
// The FileName is string compared with Expression using the following wildcard rules:
|
|
// * (asterisk) Matches zero or more characters.
|
|
// ? (question mark) Matches a single character.
|
|
// DOS_DOT (" quotation mark) Matches either a period or zero characters beyond the name string.
|
|
// DOS_QM (> greater than) Matches any single character or, upon encountering a period or end of name string, advances the expression to the end of the set of contiguous DOS_QMs.
|
|
// DOS_STAR (< less than) Matches zero or more characters until encountering and matching the final . in the name.
|
|
internal static List<FileSystemEntry> GetFiltered(List<FileSystemEntry> entries, string searchPattern)
|
|
{
|
|
if (searchPattern == String.Empty || searchPattern == "*")
|
|
{
|
|
return entries;
|
|
}
|
|
|
|
List<FileSystemEntry> result = new List<FileSystemEntry>();
|
|
if (searchPattern.EndsWith("*") && searchPattern.Length > 1)
|
|
{
|
|
string fileNameStart = searchPattern.Substring(0, searchPattern.Length - 1);
|
|
bool exactNameWithoutExtensionMatch = false;
|
|
if (fileNameStart.EndsWith("\""))
|
|
{
|
|
exactNameWithoutExtensionMatch = true;
|
|
fileNameStart = fileNameStart.Substring(0, fileNameStart.Length - 1);
|
|
}
|
|
|
|
foreach (FileSystemEntry entry in entries)
|
|
{
|
|
if (!exactNameWithoutExtensionMatch)
|
|
{
|
|
if (entry.Name.StartsWith(fileNameStart, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
result.Add(entry);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (entry.Name.StartsWith(fileNameStart + ".", StringComparison.InvariantCultureIgnoreCase) ||
|
|
entry.Name.Equals(fileNameStart, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
result.Add(entry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (searchPattern.StartsWith("<"))
|
|
{
|
|
string fileNameEnd = searchPattern.Substring(1);
|
|
foreach (FileSystemEntry entry in entries)
|
|
{
|
|
if (entry.Name.EndsWith(fileNameEnd, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
result.Add(entry);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
internal static Transaction2FindNext2Response GetSubcommandResponse(SMB1Header header, Transaction2FindNext2Request subcommand, FileSystemShare share, SMB1ConnectionState state)
|
|
{
|
|
SMB1Session session = state.GetSession(header.UID);
|
|
OpenSearch openSearch = session.GetOpenSearch(subcommand.SID);
|
|
if (openSearch == null)
|
|
{
|
|
header.Status = NTStatus.STATUS_INVALID_HANDLE;
|
|
return null;
|
|
}
|
|
|
|
bool returnResumeKeys = (subcommand.Flags & FindFlags.SMB_FIND_RETURN_RESUME_KEYS) > 0;
|
|
FindInformationList findInformationList = new FindInformationList();
|
|
for (int index = openSearch.EnumerationLocation; index < openSearch.Entries.Count; index++)
|
|
{
|
|
FindInformation infoEntry = InfoHelper.FromFileSystemEntry(openSearch.Entries[index], subcommand.InformationLevel, header.UnicodeFlag, returnResumeKeys);
|
|
findInformationList.Add(infoEntry);
|
|
if (findInformationList.GetLength(header.UnicodeFlag) > state.GetMaxDataCount(header.PID))
|
|
{
|
|
findInformationList.RemoveAt(findInformationList.Count - 1);
|
|
break;
|
|
}
|
|
|
|
if (findInformationList.Count == subcommand.SearchCount)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
int returnCount = findInformationList.Count;
|
|
Transaction2FindNext2Response response = new Transaction2FindNext2Response();
|
|
response.SetFindInformationList(findInformationList, header.UnicodeFlag);
|
|
openSearch.EnumerationLocation += returnCount;
|
|
response.EndOfSearch = (openSearch.EnumerationLocation == openSearch.Entries.Count);
|
|
if (response.EndOfSearch)
|
|
{
|
|
session.RemoveOpenSearch(subcommand.SID);
|
|
}
|
|
return response;
|
|
}
|
|
|
|
internal static Transaction2QueryFSInformationResponse GetSubcommandResponse(SMB1Header header, Transaction2QueryFSInformationRequest subcommand, FileSystemShare share)
|
|
{
|
|
Transaction2QueryFSInformationResponse response = new Transaction2QueryFSInformationResponse();
|
|
QueryFSInformation queryFSInformation = InfoHelper.GetFSInformation(subcommand.InformationLevel, share.FileSystem);
|
|
response.SetQueryFSInformation(queryFSInformation, header.UnicodeFlag);
|
|
return response;
|
|
}
|
|
|
|
internal static Transaction2QueryPathInformationResponse GetSubcommandResponse(SMB1Header header, Transaction2QueryPathInformationRequest subcommand, FileSystemShare share, SMB1ConnectionState state)
|
|
{
|
|
IFileSystem fileSystem = share.FileSystem;
|
|
string path = subcommand.FileName;
|
|
FileSystemEntry entry = fileSystem.GetEntry(path);
|
|
if (entry == null)
|
|
{
|
|
// Windows Server 2003 will return STATUS_OBJECT_NAME_NOT_FOUND
|
|
// Returning STATUS_NO_SUCH_FILE caused an issue when executing ImageX.exe from WinPE 3.0 (32-bit)
|
|
state.LogToServer(Severity.Debug, "Transaction2QueryPathInformation: File not found, Path: '{0}'", path);
|
|
header.Status = NTStatus.STATUS_OBJECT_NAME_NOT_FOUND;
|
|
return null;
|
|
}
|
|
Transaction2QueryPathInformationResponse response = new Transaction2QueryPathInformationResponse();
|
|
QueryInformation queryInformation = InfoHelper.FromFileSystemEntry(entry, false, subcommand.InformationLevel);
|
|
response.SetQueryInformation(queryInformation);
|
|
|
|
return response;
|
|
}
|
|
|
|
internal static Transaction2QueryFileInformationResponse GetSubcommandResponse(SMB1Header header, Transaction2QueryFileInformationRequest subcommand, FileSystemShare share, SMB1ConnectionState state)
|
|
{
|
|
SMB1Session session = state.GetSession(header.UID);
|
|
IFileSystem fileSystem = share.FileSystem;
|
|
OpenFileObject openFile = session.GetOpenFileObject(subcommand.FID);
|
|
if (openFile == null)
|
|
{
|
|
header.Status = NTStatus.STATUS_INVALID_HANDLE;
|
|
return null;
|
|
}
|
|
|
|
FileSystemEntry entry = fileSystem.GetEntry(openFile.Path);
|
|
if (entry == null)
|
|
{
|
|
header.Status = NTStatus.STATUS_NO_SUCH_FILE;
|
|
return null;
|
|
}
|
|
Transaction2QueryFileInformationResponse response = new Transaction2QueryFileInformationResponse();
|
|
QueryInformation queryInformation = InfoHelper.FromFileSystemEntry(entry, openFile.DeleteOnClose, subcommand.InformationLevel);
|
|
response.SetQueryInformation(queryInformation);
|
|
|
|
return response;
|
|
}
|
|
|
|
internal static Transaction2SetFileInformationResponse GetSubcommandResponse(SMB1Header header, Transaction2SetFileInformationRequest subcommand, FileSystemShare share, SMB1ConnectionState state)
|
|
{
|
|
SMB1Session session = state.GetSession(header.UID);
|
|
OpenFileObject openFile = session.GetOpenFileObject(subcommand.FID);
|
|
if (openFile == null)
|
|
{
|
|
header.Status = NTStatus.STATUS_INVALID_HANDLE;
|
|
return null;
|
|
}
|
|
|
|
Transaction2SetFileInformationResponse response = new Transaction2SetFileInformationResponse();
|
|
switch (subcommand.InformationLevel)
|
|
{
|
|
case SetInformationLevel.SMB_INFO_STANDARD:
|
|
{
|
|
return response;
|
|
}
|
|
case SetInformationLevel.SMB_INFO_SET_EAS:
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
case SetInformationLevel.SMB_SET_FILE_BASIC_INFO:
|
|
{
|
|
if (!share.HasWriteAccess(session.UserName))
|
|
{
|
|
header.Status = NTStatus.STATUS_ACCESS_DENIED;
|
|
return null;
|
|
}
|
|
|
|
SetFileBasicInfo info = (SetFileBasicInfo)subcommand.SetInfo;
|
|
bool isHidden = (info.ExtFileAttributes & ExtendedFileAttributes.Hidden) > 0;
|
|
bool isReadonly = (info.ExtFileAttributes & ExtendedFileAttributes.Readonly) > 0;
|
|
bool isArchived = (info.ExtFileAttributes & ExtendedFileAttributes.Archive) > 0;
|
|
try
|
|
{
|
|
share.FileSystem.SetAttributes(openFile.Path, isHidden, isReadonly, isArchived);
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
header.Status = NTStatus.STATUS_ACCESS_DENIED;
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
share.FileSystem.SetDates(openFile.Path, info.CreationTime, info.LastWriteTime, info.LastAccessTime);
|
|
}
|
|
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, "Transaction2SetFileInformation: Sharing violation setting file dates, Path: '{0}'", openFile.Path);
|
|
header.Status = NTStatus.STATUS_SHARING_VIOLATION;
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
header.Status = NTStatus.STATUS_DATA_ERROR;
|
|
return null;
|
|
}
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
header.Status = NTStatus.STATUS_ACCESS_DENIED;
|
|
return null;
|
|
}
|
|
return response;
|
|
}
|
|
case SetInformationLevel.SMB_SET_FILE_DISPOSITION_INFO:
|
|
{
|
|
if (((SetFileDispositionInfo)subcommand.SetInfo).DeletePending)
|
|
{
|
|
// We're supposed to delete the file on close, but it's too late to report errors at this late stage
|
|
if (!share.HasWriteAccess(session.UserName))
|
|
{
|
|
header.Status = NTStatus.STATUS_ACCESS_DENIED;
|
|
return null;
|
|
}
|
|
|
|
if (openFile.Stream != null)
|
|
{
|
|
openFile.Stream.Close();
|
|
}
|
|
try
|
|
{
|
|
state.LogToServer(Severity.Information, "NTCreate: Deleting file '{0}'", openFile.Path);
|
|
share.FileSystem.Delete(openFile.Path);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
state.LogToServer(Severity.Information, "NTCreate: Error deleting '{0}'", openFile.Path);
|
|
header.Status = NTStatus.STATUS_SHARING_VIOLATION;
|
|
return null;
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
state.LogToServer(Severity.Information, "NTCreate: Error deleting '{0}', Access Denied", openFile.Path);
|
|
header.Status = NTStatus.STATUS_ACCESS_DENIED;
|
|
return null;
|
|
}
|
|
}
|
|
return response;
|
|
}
|
|
case SetInformationLevel.SMB_SET_FILE_ALLOCATION_INFO:
|
|
{
|
|
// This subcommand is used to set the file length in bytes.
|
|
// Note: the input will NOT be a multiple of the cluster size / bytes per sector.
|
|
ulong allocationSize = ((SetFileAllocationInfo)subcommand.SetInfo).AllocationSize;
|
|
try
|
|
{
|
|
openFile.Stream.SetLength((long)allocationSize);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
state.LogToServer(Severity.Debug, "SMB_SET_FILE_ALLOCATION_INFO: Cannot set allocation for '{0}'", openFile.Path);
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
state.LogToServer(Severity.Debug, "SMB_SET_FILE_ALLOCATION_INFO: Cannot set allocation for '{0}'. Access Denied", openFile.Path);
|
|
}
|
|
return response;
|
|
}
|
|
case SetInformationLevel.SMB_SET_FILE_END_OF_FILE_INFO:
|
|
{
|
|
ulong endOfFile = ((SetFileEndOfFileInfo)subcommand.SetInfo).EndOfFile;
|
|
try
|
|
{
|
|
openFile.Stream.SetLength((long)endOfFile);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
state.LogToServer(Severity.Debug, "SMB_SET_FILE_END_OF_FILE_INFO: Cannot set end of file for '{0}'", openFile.Path);
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
state.LogToServer(Severity.Debug, "SMB_SET_FILE_END_OF_FILE_INFO: Cannot set end of file for '{0}'. Access Denied", openFile.Path);
|
|
}
|
|
return response;
|
|
}
|
|
default:
|
|
{
|
|
throw new InvalidRequestException();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|