Transaction2SubcommandHelper.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. /* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Text;
  11. using SMBLibrary.SMB1;
  12. using Utilities;
  13. namespace SMBLibrary.Server.SMB1
  14. {
  15. public class Transaction2SubcommandHelper
  16. {
  17. internal static Transaction2FindFirst2Response GetSubcommandResponse(SMB1Header header, Transaction2FindFirst2Request subcommand, FileSystemShare share, SMB1ConnectionState state)
  18. {
  19. SMB1Session session = state.GetSession(header.UID);
  20. IFileSystem fileSystem = share.FileSystem;
  21. string fileNamePattern = subcommand.FileName;
  22. List<FileSystemEntry> entries;
  23. try
  24. {
  25. entries = NTFileSystemHelper.FindEntries(fileSystem, fileNamePattern);
  26. }
  27. catch (UnauthorizedAccessException)
  28. {
  29. header.Status = NTStatus.STATUS_ACCESS_DENIED;
  30. return null;
  31. }
  32. state.LogToServer(Severity.Verbose, "FindFirst2: Searched for '{0}', found {1} matching entries", fileNamePattern, entries != null ? entries.Count.ToString() : "no");
  33. // [MS-CIFS] If no matching entries are found, the server SHOULD fail the request with STATUS_NO_SUCH_FILE.
  34. if (entries == null || entries.Count == 0)
  35. {
  36. header.Status = NTStatus.STATUS_NO_SUCH_FILE;
  37. return null;
  38. }
  39. bool returnResumeKeys = (subcommand.Flags & FindFlags.SMB_FIND_RETURN_RESUME_KEYS) > 0;
  40. int entriesToReturn = Math.Min(subcommand.SearchCount, entries.Count);
  41. // We ignore SearchAttributes
  42. FindInformationList findInformationList = new FindInformationList();
  43. for (int index = 0; index < entriesToReturn; index++)
  44. {
  45. FindInformation infoEntry = InfoHelper.FromFileSystemEntry(entries[index], subcommand.InformationLevel, header.UnicodeFlag, returnResumeKeys);
  46. findInformationList.Add(infoEntry);
  47. if (findInformationList.GetLength(header.UnicodeFlag) > state.GetMaxDataCount(header.PID))
  48. {
  49. findInformationList.RemoveAt(findInformationList.Count - 1);
  50. break;
  51. }
  52. }
  53. int returnCount = findInformationList.Count;
  54. Transaction2FindFirst2Response response = new Transaction2FindFirst2Response();
  55. response.SetFindInformationList(findInformationList, header.UnicodeFlag);
  56. response.EndOfSearch = (returnCount == entries.Count);
  57. // If [..] the search fit within a single response and SMB_FIND_CLOSE_AT_EOS is set in the Flags field,
  58. // or if SMB_FIND_CLOSE_AFTER_REQUEST is set in the request,
  59. // the server SHOULD return a SID field value of zero.
  60. // This indicates that the search has been closed and is no longer active on the server.
  61. if ((response.EndOfSearch && subcommand.CloseAtEndOfSearch) || subcommand.CloseAfterRequest)
  62. {
  63. response.SID = 0;
  64. }
  65. else
  66. {
  67. ushort? searchHandle = session.AddOpenSearch(entries, returnCount);
  68. if (!searchHandle.HasValue)
  69. {
  70. header.Status = NTStatus.STATUS_OS2_NO_MORE_SIDS;
  71. return null;
  72. }
  73. response.SID = searchHandle.Value;
  74. }
  75. return response;
  76. }
  77. internal static Transaction2FindNext2Response GetSubcommandResponse(SMB1Header header, Transaction2FindNext2Request subcommand, FileSystemShare share, SMB1ConnectionState state)
  78. {
  79. SMB1Session session = state.GetSession(header.UID);
  80. OpenSearch openSearch = session.GetOpenSearch(subcommand.SID);
  81. if (openSearch == null)
  82. {
  83. header.Status = NTStatus.STATUS_INVALID_HANDLE;
  84. return null;
  85. }
  86. bool returnResumeKeys = (subcommand.Flags & FindFlags.SMB_FIND_RETURN_RESUME_KEYS) > 0;
  87. FindInformationList findInformationList = new FindInformationList();
  88. for (int index = openSearch.EnumerationLocation; index < openSearch.Entries.Count; index++)
  89. {
  90. FindInformation infoEntry = InfoHelper.FromFileSystemEntry(openSearch.Entries[index], subcommand.InformationLevel, header.UnicodeFlag, returnResumeKeys);
  91. findInformationList.Add(infoEntry);
  92. if (findInformationList.GetLength(header.UnicodeFlag) > state.GetMaxDataCount(header.PID))
  93. {
  94. findInformationList.RemoveAt(findInformationList.Count - 1);
  95. break;
  96. }
  97. if (findInformationList.Count == subcommand.SearchCount)
  98. {
  99. break;
  100. }
  101. }
  102. int returnCount = findInformationList.Count;
  103. Transaction2FindNext2Response response = new Transaction2FindNext2Response();
  104. response.SetFindInformationList(findInformationList, header.UnicodeFlag);
  105. openSearch.EnumerationLocation += returnCount;
  106. response.EndOfSearch = (openSearch.EnumerationLocation == openSearch.Entries.Count);
  107. if (response.EndOfSearch)
  108. {
  109. session.RemoveOpenSearch(subcommand.SID);
  110. }
  111. return response;
  112. }
  113. internal static Transaction2QueryFSInformationResponse GetSubcommandResponse(SMB1Header header, Transaction2QueryFSInformationRequest subcommand, FileSystemShare share)
  114. {
  115. Transaction2QueryFSInformationResponse response = new Transaction2QueryFSInformationResponse();
  116. QueryFSInformation queryFSInformation = InfoHelper.GetFSInformation(subcommand.InformationLevel, share.FileSystem);
  117. response.SetQueryFSInformation(queryFSInformation, header.UnicodeFlag);
  118. return response;
  119. }
  120. internal static Transaction2QueryPathInformationResponse GetSubcommandResponse(SMB1Header header, Transaction2QueryPathInformationRequest subcommand, FileSystemShare share, SMB1ConnectionState state)
  121. {
  122. IFileSystem fileSystem = share.FileSystem;
  123. string path = subcommand.FileName;
  124. FileSystemEntry entry = fileSystem.GetEntry(path);
  125. if (entry == null)
  126. {
  127. // Windows Server 2003 will return STATUS_OBJECT_NAME_NOT_FOUND
  128. // Returning STATUS_NO_SUCH_FILE caused an issue when executing ImageX.exe from WinPE 3.0 (32-bit)
  129. state.LogToServer(Severity.Debug, "Transaction2QueryPathInformation: File not found, Path: '{0}'", path);
  130. header.Status = NTStatus.STATUS_OBJECT_NAME_NOT_FOUND;
  131. return null;
  132. }
  133. Transaction2QueryPathInformationResponse response = new Transaction2QueryPathInformationResponse();
  134. QueryInformation queryInformation = InfoHelper.FromFileSystemEntry(entry, false, subcommand.InformationLevel);
  135. response.SetQueryInformation(queryInformation);
  136. return response;
  137. }
  138. internal static Transaction2QueryFileInformationResponse GetSubcommandResponse(SMB1Header header, Transaction2QueryFileInformationRequest subcommand, FileSystemShare share, SMB1ConnectionState state)
  139. {
  140. SMB1Session session = state.GetSession(header.UID);
  141. IFileSystem fileSystem = share.FileSystem;
  142. OpenFileObject openFile = session.GetOpenFileObject(subcommand.FID);
  143. if (openFile == null)
  144. {
  145. header.Status = NTStatus.STATUS_INVALID_HANDLE;
  146. return null;
  147. }
  148. FileSystemEntry entry = fileSystem.GetEntry(openFile.Path);
  149. if (entry == null)
  150. {
  151. header.Status = NTStatus.STATUS_NO_SUCH_FILE;
  152. return null;
  153. }
  154. Transaction2QueryFileInformationResponse response = new Transaction2QueryFileInformationResponse();
  155. QueryInformation queryInformation = InfoHelper.FromFileSystemEntry(entry, openFile.DeleteOnClose, subcommand.InformationLevel);
  156. response.SetQueryInformation(queryInformation);
  157. return response;
  158. }
  159. internal static Transaction2SetFileInformationResponse GetSubcommandResponse(SMB1Header header, Transaction2SetFileInformationRequest subcommand, FileSystemShare share, SMB1ConnectionState state)
  160. {
  161. SMB1Session session = state.GetSession(header.UID);
  162. OpenFileObject openFile = session.GetOpenFileObject(subcommand.FID);
  163. if (openFile == null)
  164. {
  165. header.Status = NTStatus.STATUS_INVALID_HANDLE;
  166. return null;
  167. }
  168. Transaction2SetFileInformationResponse response = new Transaction2SetFileInformationResponse();
  169. switch (subcommand.InformationLevel)
  170. {
  171. case SetInformationLevel.SMB_INFO_STANDARD:
  172. {
  173. return response;
  174. }
  175. case SetInformationLevel.SMB_INFO_SET_EAS:
  176. {
  177. throw new NotImplementedException();
  178. }
  179. case SetInformationLevel.SMB_SET_FILE_BASIC_INFO:
  180. {
  181. if (!share.HasWriteAccess(session.UserName))
  182. {
  183. header.Status = NTStatus.STATUS_ACCESS_DENIED;
  184. return null;
  185. }
  186. SetFileBasicInfo info = (SetFileBasicInfo)subcommand.SetInfo;
  187. bool isHidden = (info.ExtFileAttributes & ExtendedFileAttributes.Hidden) > 0;
  188. bool isReadonly = (info.ExtFileAttributes & ExtendedFileAttributes.Readonly) > 0;
  189. bool isArchived = (info.ExtFileAttributes & ExtendedFileAttributes.Archive) > 0;
  190. try
  191. {
  192. share.FileSystem.SetAttributes(openFile.Path, isHidden, isReadonly, isArchived);
  193. }
  194. catch (UnauthorizedAccessException)
  195. {
  196. header.Status = NTStatus.STATUS_ACCESS_DENIED;
  197. return null;
  198. }
  199. try
  200. {
  201. share.FileSystem.SetDates(openFile.Path, info.CreationTime, info.LastWriteTime, info.LastAccessTime);
  202. }
  203. catch (IOException ex)
  204. {
  205. ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex);
  206. if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION)
  207. {
  208. // Returning STATUS_SHARING_VIOLATION is undocumented but apparently valid
  209. state.LogToServer(Severity.Debug, "Transaction2SetFileInformation: Sharing violation setting file dates, Path: '{0}'", openFile.Path);
  210. header.Status = NTStatus.STATUS_SHARING_VIOLATION;
  211. return null;
  212. }
  213. else
  214. {
  215. header.Status = NTStatus.STATUS_DATA_ERROR;
  216. return null;
  217. }
  218. }
  219. catch (UnauthorizedAccessException)
  220. {
  221. header.Status = NTStatus.STATUS_ACCESS_DENIED;
  222. return null;
  223. }
  224. return response;
  225. }
  226. case SetInformationLevel.SMB_SET_FILE_DISPOSITION_INFO:
  227. {
  228. if (((SetFileDispositionInfo)subcommand.SetInfo).DeletePending)
  229. {
  230. // We're supposed to delete the file on close, but it's too late to report errors at this late stage
  231. if (!share.HasWriteAccess(session.UserName))
  232. {
  233. header.Status = NTStatus.STATUS_ACCESS_DENIED;
  234. return null;
  235. }
  236. if (openFile.Stream != null)
  237. {
  238. openFile.Stream.Close();
  239. }
  240. try
  241. {
  242. state.LogToServer(Severity.Information, "NTCreate: Deleting file '{0}'", openFile.Path);
  243. share.FileSystem.Delete(openFile.Path);
  244. }
  245. catch (IOException)
  246. {
  247. state.LogToServer(Severity.Information, "NTCreate: Error deleting '{0}'", openFile.Path);
  248. header.Status = NTStatus.STATUS_SHARING_VIOLATION;
  249. return null;
  250. }
  251. catch (UnauthorizedAccessException)
  252. {
  253. state.LogToServer(Severity.Information, "NTCreate: Error deleting '{0}', Access Denied", openFile.Path);
  254. header.Status = NTStatus.STATUS_ACCESS_DENIED;
  255. return null;
  256. }
  257. }
  258. return response;
  259. }
  260. case SetInformationLevel.SMB_SET_FILE_ALLOCATION_INFO:
  261. {
  262. // This subcommand is used to set the file length in bytes.
  263. // Note: the input will NOT be a multiple of the cluster size / bytes per sector.
  264. ulong allocationSize = ((SetFileAllocationInfo)subcommand.SetInfo).AllocationSize;
  265. try
  266. {
  267. openFile.Stream.SetLength((long)allocationSize);
  268. }
  269. catch (IOException)
  270. {
  271. state.LogToServer(Severity.Debug, "SMB_SET_FILE_ALLOCATION_INFO: Cannot set allocation for '{0}'", openFile.Path);
  272. }
  273. catch (UnauthorizedAccessException)
  274. {
  275. state.LogToServer(Severity.Debug, "SMB_SET_FILE_ALLOCATION_INFO: Cannot set allocation for '{0}'. Access Denied", openFile.Path);
  276. }
  277. return response;
  278. }
  279. case SetInformationLevel.SMB_SET_FILE_END_OF_FILE_INFO:
  280. {
  281. ulong endOfFile = ((SetFileEndOfFileInfo)subcommand.SetInfo).EndOfFile;
  282. try
  283. {
  284. openFile.Stream.SetLength((long)endOfFile);
  285. }
  286. catch (IOException)
  287. {
  288. state.LogToServer(Severity.Debug, "SMB_SET_FILE_END_OF_FILE_INFO: Cannot set end of file for '{0}'", openFile.Path);
  289. }
  290. catch (UnauthorizedAccessException)
  291. {
  292. state.LogToServer(Severity.Debug, "SMB_SET_FILE_END_OF_FILE_INFO: Cannot set end of file for '{0}'. Access Denied", openFile.Path);
  293. }
  294. return response;
  295. }
  296. default:
  297. {
  298. throw new InvalidRequestException();
  299. }
  300. }
  301. }
  302. }
  303. }