Transaction2SubcommandHelper.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  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. // Windows servers will return "." and ".." when enumerating directory files, Windows clients do not require it.
  18. // 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).
  19. public const bool IncludeCurrentDirectoryInResults = true;
  20. public const bool IncludeParentDirectoryInResults = true;
  21. internal static Transaction2FindFirst2Response GetSubcommandResponse(SMB1Header header, Transaction2FindFirst2Request subcommand, FileSystemShare share, SMB1ConnectionState state)
  22. {
  23. IFileSystem fileSystem = share.FileSystem;
  24. string path = subcommand.FileName;
  25. // '\Directory' - Get the directory info
  26. // '\Directory\*' - List the directory files
  27. // '\Directory\s*' - List the directory files starting with s (cmd.exe will use this syntax when entering 's' and hitting tab for autocomplete)
  28. // '\Directory\<.inf' (Update driver will use this syntax)
  29. // '\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)
  30. bool isDirectoryEnumeration = false;
  31. string searchPattern = String.Empty;
  32. if (path.Contains("*") || path.Contains("<"))
  33. {
  34. isDirectoryEnumeration = true;
  35. int separatorIndex = path.LastIndexOf('\\');
  36. searchPattern = path.Substring(separatorIndex + 1);
  37. path = path.Substring(0, separatorIndex + 1);
  38. }
  39. bool exactNameWithoutExtension = searchPattern.Contains("\"");
  40. FileSystemEntry entry = fileSystem.GetEntry(path);
  41. if (entry == null)
  42. {
  43. header.Status = NTStatus.STATUS_NO_SUCH_FILE;
  44. return null;
  45. }
  46. List<FileSystemEntry> entries;
  47. if (isDirectoryEnumeration)
  48. {
  49. try
  50. {
  51. entries = fileSystem.ListEntriesInDirectory(path);
  52. }
  53. catch (UnauthorizedAccessException)
  54. {
  55. header.Status = NTStatus.STATUS_ACCESS_DENIED;
  56. return null;
  57. }
  58. if (searchPattern != String.Empty)
  59. {
  60. entries = GetFiltered(entries, searchPattern);
  61. }
  62. if (!exactNameWithoutExtension)
  63. {
  64. if (IncludeParentDirectoryInResults)
  65. {
  66. entries.Insert(0, fileSystem.GetEntry(FileSystem.GetParentDirectory(path)));
  67. entries[0].Name = "..";
  68. }
  69. if (IncludeCurrentDirectoryInResults)
  70. {
  71. entries.Insert(0, fileSystem.GetEntry(path));
  72. entries[0].Name = ".";
  73. }
  74. }
  75. // If no matching entries are found, the server SHOULD fail the request with STATUS_NO_SUCH_FILE.
  76. if (entries.Count == 0)
  77. {
  78. header.Status = NTStatus.STATUS_NO_SUCH_FILE;
  79. return null;
  80. }
  81. }
  82. else
  83. {
  84. entries = new List<FileSystemEntry>();
  85. entries.Add(entry);
  86. }
  87. bool returnResumeKeys = (subcommand.Flags & FindFlags.SMB_FIND_RETURN_RESUME_KEYS) > 0;
  88. int entriesToReturn = Math.Min(subcommand.SearchCount, entries.Count);
  89. // We ignore SearchAttributes
  90. FindInformationList findInformationList = new FindInformationList();
  91. for (int index = 0; index < entriesToReturn; index++)
  92. {
  93. FindInformation infoEntry = InfoHelper.FromFileSystemEntry(entries[index], subcommand.InformationLevel, header.UnicodeFlag, returnResumeKeys);
  94. findInformationList.Add(infoEntry);
  95. if (findInformationList.GetLength(header.UnicodeFlag) > state.GetMaxDataCount(header.PID))
  96. {
  97. findInformationList.RemoveAt(findInformationList.Count - 1);
  98. break;
  99. }
  100. }
  101. int returnCount = findInformationList.Count;
  102. Transaction2FindFirst2Response response = new Transaction2FindFirst2Response();
  103. response.SetFindInformationList(findInformationList, header.UnicodeFlag);
  104. response.EndOfSearch = (returnCount == entries.Count) && (entries.Count <= subcommand.SearchCount);
  105. bool closeAtEndOfSearch = (subcommand.Flags & FindFlags.SMB_FIND_CLOSE_AT_EOS) > 0;
  106. bool closeAfterRequest = (subcommand.Flags & FindFlags.SMB_FIND_CLOSE_AFTER_REQUEST) > 0;
  107. // If [..] the search fit within a single response and SMB_FIND_CLOSE_AT_EOS is set in the Flags field,
  108. // or if SMB_FIND_CLOSE_AFTER_REQUEST is set in the request,
  109. // the server SHOULD return a SID field value of zero.
  110. // This indicates that the search has been closed and is no longer active on the server.
  111. if ((response.EndOfSearch && closeAtEndOfSearch) || closeAfterRequest)
  112. {
  113. response.SID = 0;
  114. }
  115. else
  116. {
  117. ushort? searchHandle = state.AllocateSearchHandle();
  118. if (!searchHandle.HasValue)
  119. {
  120. header.Status = NTStatus.STATUS_OS2_NO_MORE_SIDS;
  121. return null;
  122. }
  123. response.SID = searchHandle.Value;
  124. entries.RemoveRange(0, returnCount);
  125. state.OpenSearches.Add(response.SID, entries);
  126. }
  127. return response;
  128. }
  129. // [MS-FSA] 2.1.4.4
  130. // The FileName is string compared with Expression using the following wildcard rules:
  131. // * (asterisk) Matches zero or more characters.
  132. // ? (question mark) Matches a single character.
  133. // DOS_DOT (" quotation mark) Matches either a period or zero characters beyond the name string.
  134. // 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.
  135. // DOS_STAR (< less than) Matches zero or more characters until encountering and matching the final . in the name.
  136. internal static List<FileSystemEntry> GetFiltered(List<FileSystemEntry> entries, string searchPattern)
  137. {
  138. if (searchPattern == String.Empty || searchPattern == "*")
  139. {
  140. return entries;
  141. }
  142. List<FileSystemEntry> result = new List<FileSystemEntry>();
  143. if (searchPattern.EndsWith("*") && searchPattern.Length > 1)
  144. {
  145. string fileNameStart = searchPattern.Substring(0, searchPattern.Length - 1);
  146. bool exactNameWithoutExtensionMatch = false;
  147. if (fileNameStart.EndsWith("\""))
  148. {
  149. exactNameWithoutExtensionMatch = true;
  150. fileNameStart = fileNameStart.Substring(0, fileNameStart.Length - 1);
  151. }
  152. foreach (FileSystemEntry entry in entries)
  153. {
  154. if (!exactNameWithoutExtensionMatch)
  155. {
  156. if (entry.Name.StartsWith(fileNameStart, StringComparison.InvariantCultureIgnoreCase))
  157. {
  158. result.Add(entry);
  159. }
  160. }
  161. else
  162. {
  163. if (entry.Name.StartsWith(fileNameStart + ".", StringComparison.InvariantCultureIgnoreCase) ||
  164. entry.Name.Equals(fileNameStart, StringComparison.InvariantCultureIgnoreCase))
  165. {
  166. result.Add(entry);
  167. }
  168. }
  169. }
  170. }
  171. else if (searchPattern.StartsWith("<"))
  172. {
  173. string fileNameEnd = searchPattern.Substring(1);
  174. foreach (FileSystemEntry entry in entries)
  175. {
  176. if (entry.Name.EndsWith(fileNameEnd, StringComparison.InvariantCultureIgnoreCase))
  177. {
  178. result.Add(entry);
  179. }
  180. }
  181. }
  182. return result;
  183. }
  184. internal static Transaction2FindNext2Response GetSubcommandResponse(SMB1Header header, Transaction2FindNext2Request subcommand, FileSystemShare share, SMB1ConnectionState state)
  185. {
  186. if (!state.OpenSearches.ContainsKey(subcommand.SID))
  187. {
  188. header.Status = NTStatus.STATUS_INVALID_HANDLE;
  189. return null;
  190. }
  191. bool returnResumeKeys = (subcommand.Flags & FindFlags.SMB_FIND_RETURN_RESUME_KEYS) > 0;
  192. List<FileSystemEntry> entries = state.OpenSearches[subcommand.SID];
  193. FindInformationList findInformationList = new FindInformationList();
  194. for (int index = 0; index < entries.Count; index++)
  195. {
  196. FindInformation infoEntry = InfoHelper.FromFileSystemEntry(entries[index], subcommand.InformationLevel, header.UnicodeFlag, returnResumeKeys);
  197. findInformationList.Add(infoEntry);
  198. if (findInformationList.GetLength(header.UnicodeFlag) > state.GetMaxDataCount(header.PID))
  199. {
  200. findInformationList.RemoveAt(findInformationList.Count - 1);
  201. break;
  202. }
  203. }
  204. int returnCount = findInformationList.Count;
  205. Transaction2FindNext2Response response = new Transaction2FindNext2Response();
  206. response.SetFindInformationList(findInformationList, header.UnicodeFlag);
  207. entries.RemoveRange(0, returnCount);
  208. state.OpenSearches[subcommand.SID] = entries;
  209. response.EndOfSearch = (returnCount == entries.Count) && (entries.Count <= subcommand.SearchCount);
  210. if (response.EndOfSearch)
  211. {
  212. state.ReleaseSearchHandle(subcommand.SID);
  213. }
  214. return response;
  215. }
  216. internal static Transaction2QueryFSInformationResponse GetSubcommandResponse(SMB1Header header, Transaction2QueryFSInformationRequest subcommand, FileSystemShare share)
  217. {
  218. Transaction2QueryFSInformationResponse response = new Transaction2QueryFSInformationResponse();
  219. QueryFSInformation queryFSInformation = InfoHelper.GetFSInformation(subcommand.InformationLevel, share.FileSystem);
  220. response.SetQueryFSInformation(queryFSInformation, header.UnicodeFlag);
  221. return response;
  222. }
  223. internal static Transaction2QueryPathInformationResponse GetSubcommandResponse(SMB1Header header, Transaction2QueryPathInformationRequest subcommand, FileSystemShare share, SMB1ConnectionState state)
  224. {
  225. IFileSystem fileSystem = share.FileSystem;
  226. string path = subcommand.FileName;
  227. FileSystemEntry entry = fileSystem.GetEntry(path);
  228. if (entry == null)
  229. {
  230. // Windows Server 2003 will return STATUS_OBJECT_NAME_NOT_FOUND
  231. // Returning STATUS_NO_SUCH_FILE caused an issue when executing ImageX.exe from WinPE 3.0 (32-bit)
  232. state.LogToServer(Severity.Debug, "Transaction2QueryPathInformation: File not found, Path: '{0}'", path);
  233. header.Status = NTStatus.STATUS_OBJECT_NAME_NOT_FOUND;
  234. return null;
  235. }
  236. Transaction2QueryPathInformationResponse response = new Transaction2QueryPathInformationResponse();
  237. QueryInformation queryInformation = InfoHelper.FromFileSystemEntry(entry, subcommand.InformationLevel);
  238. response.SetQueryInformation(queryInformation);
  239. return response;
  240. }
  241. internal static Transaction2QueryFileInformationResponse GetSubcommandResponse(SMB1Header header, Transaction2QueryFileInformationRequest subcommand, FileSystemShare share, SMB1ConnectionState state)
  242. {
  243. IFileSystem fileSystem = share.FileSystem;
  244. string openedFilePath = state.GetOpenedFilePath(subcommand.FID);
  245. if (openedFilePath == null)
  246. {
  247. header.Status = NTStatus.STATUS_INVALID_HANDLE;
  248. return null;
  249. }
  250. FileSystemEntry entry = fileSystem.GetEntry(openedFilePath);
  251. if (entry == null)
  252. {
  253. header.Status = NTStatus.STATUS_NO_SUCH_FILE;
  254. return null;
  255. }
  256. Transaction2QueryFileInformationResponse response = new Transaction2QueryFileInformationResponse();
  257. QueryInformation queryInformation = InfoHelper.FromFileSystemEntry(entry, subcommand.InformationLevel);
  258. response.SetQueryInformation(queryInformation);
  259. return response;
  260. }
  261. internal static Transaction2SetFileInformationResponse GetSubcommandResponse(SMB1Header header, Transaction2SetFileInformationRequest subcommand, FileSystemShare share, SMB1ConnectionState state)
  262. {
  263. string openedFilePath = state.GetOpenedFilePath(subcommand.FID);
  264. if (openedFilePath == null)
  265. {
  266. header.Status = NTStatus.STATUS_INVALID_HANDLE;
  267. return null;
  268. }
  269. OpenedFileObject fileObject = state.GetOpenedFileObject(subcommand.FID);
  270. Transaction2SetFileInformationResponse response = new Transaction2SetFileInformationResponse();
  271. switch (subcommand.InformationLevel)
  272. {
  273. case SetInformationLevel.SMB_INFO_STANDARD:
  274. {
  275. return response;
  276. }
  277. case SetInformationLevel.SMB_INFO_SET_EAS:
  278. {
  279. throw new NotImplementedException();
  280. }
  281. case SetInformationLevel.SMB_SET_FILE_BASIC_INFO:
  282. {
  283. string userName = state.GetConnectedUserName(header.UID);
  284. if (!share.HasWriteAccess(userName))
  285. {
  286. header.Status = NTStatus.STATUS_ACCESS_DENIED;
  287. return null;
  288. }
  289. SetFileBasicInfo info = (SetFileBasicInfo)subcommand.SetInfo;
  290. bool isHidden = (info.ExtFileAttributes & ExtendedFileAttributes.Hidden) > 0;
  291. bool isReadonly = (info.ExtFileAttributes & ExtendedFileAttributes.Readonly) > 0;
  292. bool isArchived = (info.ExtFileAttributes & ExtendedFileAttributes.Archive) > 0;
  293. try
  294. {
  295. share.FileSystem.SetAttributes(openedFilePath, isHidden, isReadonly, isArchived);
  296. }
  297. catch (UnauthorizedAccessException)
  298. {
  299. header.Status = NTStatus.STATUS_ACCESS_DENIED;
  300. return null;
  301. }
  302. DateTime? creationTime = null;
  303. DateTime? lastWriteDT = null;
  304. DateTime? lastAccessTime = null;
  305. if (info.CreationTime != SMB1Helper.FileTimeNotSpecified)
  306. {
  307. creationTime = info.CreationTime;
  308. }
  309. if (info.LastWriteTime != SMB1Helper.FileTimeNotSpecified)
  310. {
  311. lastWriteDT = info.LastWriteTime;
  312. }
  313. if (info.LastAccessTime != SMB1Helper.FileTimeNotSpecified)
  314. {
  315. lastAccessTime = info.LastAccessTime;
  316. }
  317. try
  318. {
  319. share.FileSystem.SetDates(openedFilePath, creationTime, lastWriteDT, lastAccessTime);
  320. }
  321. catch (IOException ex)
  322. {
  323. ushort errorCode = IOExceptionHelper.GetWin32ErrorCode(ex);
  324. if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION)
  325. {
  326. // Returning STATUS_SHARING_VIOLATION is undocumented but apparently valid
  327. state.LogToServer(Severity.Debug, "Transaction2SetFileInformation: Sharing violation setting file dates, Path: '{0}'", openedFilePath);
  328. header.Status = NTStatus.STATUS_SHARING_VIOLATION;
  329. return null;
  330. }
  331. else
  332. {
  333. header.Status = NTStatus.STATUS_DATA_ERROR;
  334. return null;
  335. }
  336. }
  337. catch (UnauthorizedAccessException)
  338. {
  339. header.Status = NTStatus.STATUS_ACCESS_DENIED;
  340. return null;
  341. }
  342. return response;
  343. }
  344. case SetInformationLevel.SMB_SET_FILE_DISPOSITION_INFO:
  345. {
  346. if (((SetFileDispositionInfo)subcommand.SetInfo).DeletePending)
  347. {
  348. // We're supposed to delete the file on close, but it's too late to report errors at this late stage
  349. string userName = state.GetConnectedUserName(header.UID);
  350. if (!share.HasWriteAccess(userName))
  351. {
  352. header.Status = NTStatus.STATUS_ACCESS_DENIED;
  353. return null;
  354. }
  355. if (fileObject.Stream != null)
  356. {
  357. fileObject.Stream.Close();
  358. }
  359. try
  360. {
  361. state.LogToServer(Severity.Information, "NTCreate: Deleting file '{0}'", openedFilePath);
  362. share.FileSystem.Delete(openedFilePath);
  363. }
  364. catch (IOException)
  365. {
  366. state.LogToServer(Severity.Information, "NTCreate: Error deleting '{0}'", openedFilePath);
  367. header.Status = NTStatus.STATUS_SHARING_VIOLATION;
  368. return null;
  369. }
  370. catch (UnauthorizedAccessException)
  371. {
  372. state.LogToServer(Severity.Information, "NTCreate: Error deleting '{0}', Access Denied", openedFilePath);
  373. header.Status = NTStatus.STATUS_ACCESS_DENIED;
  374. return null;
  375. }
  376. }
  377. return response;
  378. }
  379. case SetInformationLevel.SMB_SET_FILE_ALLOCATION_INFO:
  380. {
  381. // This subcommand is used to set the file length in bytes.
  382. // Note: the input will NOT be a multiple of the cluster size / bytes per sector.
  383. ulong allocationSize = ((SetFileAllocationInfo)subcommand.SetInfo).AllocationSize;
  384. try
  385. {
  386. fileObject.Stream.SetLength((long)allocationSize);
  387. }
  388. catch (IOException)
  389. {
  390. state.LogToServer(Severity.Debug, "SMB_SET_FILE_ALLOCATION_INFO: Cannot set allocation for '{0}'", openedFilePath);
  391. }
  392. catch (UnauthorizedAccessException)
  393. {
  394. state.LogToServer(Severity.Debug, "SMB_SET_FILE_ALLOCATION_INFO: Cannot set allocation for '{0}'. Access Denied", openedFilePath);
  395. }
  396. return response;
  397. }
  398. case SetInformationLevel.SMB_SET_FILE_END_OF_FILE_INFO:
  399. {
  400. ulong endOfFile = ((SetFileEndOfFileInfo)subcommand.SetInfo).EndOfFile;
  401. try
  402. {
  403. fileObject.Stream.SetLength((long)endOfFile);
  404. }
  405. catch (IOException)
  406. {
  407. state.LogToServer(Severity.Debug, "SMB_SET_FILE_END_OF_FILE_INFO: Cannot set end of file for '{0}'", openedFilePath);
  408. }
  409. catch (UnauthorizedAccessException)
  410. {
  411. state.LogToServer(Severity.Debug, "SMB_SET_FILE_END_OF_FILE_INFO: Cannot set end of file for '{0}'. Access Denied", openedFilePath);
  412. }
  413. return response;
  414. }
  415. default:
  416. {
  417. throw new InvalidRequestException();
  418. }
  419. }
  420. }
  421. }
  422. }