NTFileSystemHelper.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  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 SMBLibrary.Services;
  11. using Utilities;
  12. namespace SMBLibrary.Server
  13. {
  14. /// <summary>
  15. /// Helper class to access the FileSystemShare / NamedPipeShare in an NT-like manner dictated by the SMB protocol
  16. /// </summary>
  17. public partial class NTFileSystemHelper
  18. {
  19. public const int BytesPerSector = 512;
  20. public const int ClusterSize = 4096;
  21. public static NTStatus CreateFile(out FileSystemEntry entry, out Stream stream, out FileStatus fileStatus, IFileSystem fileSystem, string path, AccessMask desiredAccess, ShareAccess shareAccess, CreateDisposition createDisposition, CreateOptions createOptions, ConnectionState state)
  22. {
  23. fileStatus = FileStatus.FILE_DOES_NOT_EXIST;
  24. stream = null;
  25. FileAccess createAccess = NTFileStoreHelper.ToCreateFileAccess(desiredAccess, createDisposition);
  26. bool requestedWriteAccess = (createAccess & FileAccess.Write) > 0;
  27. bool forceDirectory = (createOptions & CreateOptions.FILE_DIRECTORY_FILE) > 0;
  28. bool forceFile = (createOptions & CreateOptions.FILE_NON_DIRECTORY_FILE) > 0;
  29. if (forceDirectory & (createDisposition != CreateDisposition.FILE_CREATE &&
  30. createDisposition != CreateDisposition.FILE_OPEN &&
  31. createDisposition != CreateDisposition.FILE_OPEN_IF &&
  32. createDisposition != CreateDisposition.FILE_SUPERSEDE))
  33. {
  34. entry = null;
  35. return NTStatus.STATUS_INVALID_PARAMETER;
  36. }
  37. // Windows will try to access named streams (alternate data streams) regardless of the FILE_NAMED_STREAMS flag, we need to prevent this behaviour.
  38. if (path.Contains(":"))
  39. {
  40. // Windows Server 2003 will return STATUS_OBJECT_NAME_NOT_FOUND
  41. entry = null;
  42. return NTStatus.STATUS_NO_SUCH_FILE;
  43. }
  44. try
  45. {
  46. entry = fileSystem.GetEntry(path);
  47. }
  48. catch (Exception ex)
  49. {
  50. NTStatus status = ToNTStatus(ex);
  51. state.LogToServer(Severity.Debug, "CreateFile: Error retrieving '{0}'. {1}.", path, status);
  52. entry = null;
  53. return status;
  54. }
  55. if (createDisposition == CreateDisposition.FILE_OPEN)
  56. {
  57. if (entry == null)
  58. {
  59. return NTStatus.STATUS_OBJECT_PATH_NOT_FOUND;
  60. }
  61. fileStatus = FileStatus.FILE_EXISTS;
  62. if (entry.IsDirectory && forceFile)
  63. {
  64. return NTStatus.STATUS_FILE_IS_A_DIRECTORY;
  65. }
  66. if (!entry.IsDirectory && forceDirectory)
  67. {
  68. // Not sure if that's the correct response
  69. return NTStatus.STATUS_OBJECT_NAME_COLLISION;
  70. }
  71. }
  72. else if (createDisposition == CreateDisposition.FILE_CREATE)
  73. {
  74. if (entry != null)
  75. {
  76. // File already exists, fail the request
  77. state.LogToServer(Severity.Debug, "CreateFile: File '{0}' already exist", path);
  78. fileStatus = FileStatus.FILE_EXISTS;
  79. return NTStatus.STATUS_OBJECT_NAME_COLLISION;
  80. }
  81. if (!requestedWriteAccess)
  82. {
  83. return NTStatus.STATUS_ACCESS_DENIED;
  84. }
  85. try
  86. {
  87. if (forceDirectory)
  88. {
  89. state.LogToServer(Severity.Information, "CreateFile: Creating directory '{0}'", path);
  90. entry = fileSystem.CreateDirectory(path);
  91. }
  92. else
  93. {
  94. state.LogToServer(Severity.Information, "CreateFile: Creating file '{0}'", path);
  95. entry = fileSystem.CreateFile(path);
  96. }
  97. }
  98. catch (Exception ex)
  99. {
  100. NTStatus status = ToNTStatus(ex);
  101. state.LogToServer(Severity.Debug, "CreateFile: Error creating '{0}'. {1}.", path, status);
  102. return status;
  103. }
  104. fileStatus = FileStatus.FILE_CREATED;
  105. }
  106. else if (createDisposition == CreateDisposition.FILE_OPEN_IF ||
  107. createDisposition == CreateDisposition.FILE_OVERWRITE ||
  108. createDisposition == CreateDisposition.FILE_OVERWRITE_IF ||
  109. createDisposition == CreateDisposition.FILE_SUPERSEDE)
  110. {
  111. if (entry == null)
  112. {
  113. if (createDisposition == CreateDisposition.FILE_OVERWRITE)
  114. {
  115. return NTStatus.STATUS_OBJECT_PATH_NOT_FOUND;
  116. }
  117. if (!requestedWriteAccess)
  118. {
  119. return NTStatus.STATUS_ACCESS_DENIED;
  120. }
  121. try
  122. {
  123. if (forceDirectory)
  124. {
  125. state.LogToServer(Severity.Information, "CreateFile: Creating directory '{0}'", path);
  126. entry = fileSystem.CreateDirectory(path);
  127. }
  128. else
  129. {
  130. state.LogToServer(Severity.Information, "CreateFile: Creating file '{0}'", path);
  131. entry = fileSystem.CreateFile(path);
  132. }
  133. }
  134. catch (Exception ex)
  135. {
  136. NTStatus status = ToNTStatus(ex);
  137. state.LogToServer(Severity.Debug, "CreateFile: Error creating '{0}'. {1}.", path, status);
  138. return status;
  139. }
  140. fileStatus = FileStatus.FILE_CREATED;
  141. }
  142. else
  143. {
  144. fileStatus = FileStatus.FILE_EXISTS;
  145. if (!requestedWriteAccess)
  146. {
  147. return NTStatus.STATUS_ACCESS_DENIED;
  148. }
  149. if (createDisposition == CreateDisposition.FILE_OVERWRITE ||
  150. createDisposition == CreateDisposition.FILE_OVERWRITE_IF)
  151. {
  152. // Truncate the file
  153. try
  154. {
  155. Stream temp = fileSystem.OpenFile(path, FileMode.Truncate, FileAccess.ReadWrite, FileShare.ReadWrite);
  156. temp.Close();
  157. }
  158. catch (Exception ex)
  159. {
  160. NTStatus status = ToNTStatus(ex);
  161. state.LogToServer(Severity.Debug, "CreateFile: Error truncating '{0}'. {1}.", path, status);
  162. return status;
  163. }
  164. fileStatus = FileStatus.FILE_OVERWRITTEN;
  165. }
  166. else if (createDisposition == CreateDisposition.FILE_SUPERSEDE)
  167. {
  168. // Delete the old file
  169. try
  170. {
  171. fileSystem.Delete(path);
  172. }
  173. catch(Exception ex)
  174. {
  175. NTStatus status = ToNTStatus(ex);
  176. state.LogToServer(Severity.Debug, "CreateFile: Error deleting '{0}'. {1}.", path, status);
  177. return status;
  178. }
  179. try
  180. {
  181. if (forceDirectory)
  182. {
  183. state.LogToServer(Severity.Information, "CreateFile: Creating directory '{0}'", path);
  184. entry = fileSystem.CreateDirectory(path);
  185. }
  186. else
  187. {
  188. state.LogToServer(Severity.Information, "CreateFile: Creating file '{0}'", path);
  189. entry = fileSystem.CreateFile(path);
  190. }
  191. }
  192. catch (Exception ex)
  193. {
  194. NTStatus status = ToNTStatus(ex);
  195. state.LogToServer(Severity.Debug, "CreateFile: Error creating '{0}'. {1}.", path, status);
  196. return status;
  197. }
  198. fileStatus = FileStatus.FILE_SUPERSEDED;
  199. }
  200. }
  201. }
  202. else
  203. {
  204. return NTStatus.STATUS_INVALID_PARAMETER;
  205. }
  206. FileAccess fileAccess = NTFileStoreHelper.ToFileAccess(desiredAccess.File);
  207. bool deleteOnClose = false;
  208. if (fileAccess == (FileAccess)0 || entry.IsDirectory)
  209. {
  210. stream = null;
  211. }
  212. else
  213. {
  214. deleteOnClose = (createOptions & CreateOptions.FILE_DELETE_ON_CLOSE) > 0;
  215. NTStatus openStatus = OpenFileStream(out stream, fileSystem, path, fileAccess, shareAccess, createOptions, state);
  216. if (openStatus != NTStatus.STATUS_SUCCESS)
  217. {
  218. return openStatus;
  219. }
  220. }
  221. if (fileStatus != FileStatus.FILE_CREATED &&
  222. fileStatus != FileStatus.FILE_OVERWRITTEN &&
  223. fileStatus != FileStatus.FILE_SUPERSEDED)
  224. {
  225. fileStatus = FileStatus.FILE_OPENED;
  226. }
  227. return NTStatus.STATUS_SUCCESS;
  228. }
  229. public static NTStatus OpenFileStream(out Stream stream, IFileSystem fileSystem, string path, FileAccess fileAccess, ShareAccess shareAccess, CreateOptions openOptions, ConnectionState state)
  230. {
  231. stream = null;
  232. // When FILE_OPEN_REPARSE_POINT is specified, the operation should continue normally if the file is not a reparse point.
  233. // FILE_OPEN_REPARSE_POINT is a hint that the caller does not intend to actually read the file, with the exception
  234. // of a file copy operation (where the caller will attempt to simply copy the reparse point).
  235. bool openReparsePoint = (openOptions & CreateOptions.FILE_OPEN_REPARSE_POINT) > 0;
  236. bool disableBuffering = (openOptions & CreateOptions.FILE_NO_INTERMEDIATE_BUFFERING) > 0;
  237. bool buffered = (openOptions & CreateOptions.FILE_SEQUENTIAL_ONLY) > 0 && !disableBuffering && !openReparsePoint;
  238. FileShare fileShare = NTFileStoreHelper.ToFileShare(shareAccess);
  239. state.LogToServer(Severity.Verbose, "OpenFile: Opening '{0}', Access={1}, Share={2}, Buffered={3}", path, fileAccess, fileShare, buffered);
  240. try
  241. {
  242. stream = fileSystem.OpenFile(path, FileMode.Open, fileAccess, fileShare);
  243. }
  244. catch (Exception ex)
  245. {
  246. NTStatus status = ToNTStatus(ex);
  247. state.LogToServer(Severity.Debug, "OpenFile: Cannot open '{0}'. {1}.", path, status);
  248. return status;
  249. }
  250. if (buffered)
  251. {
  252. stream = new PrefetchedStream(stream);
  253. }
  254. return NTStatus.STATUS_SUCCESS;
  255. }
  256. public static NTStatus ReadFile(out byte[] data, OpenFileObject openFile, long offset, int maxCount, ConnectionState state)
  257. {
  258. data = null;
  259. string openFilePath = openFile.Path;
  260. Stream stream = openFile.Stream;
  261. if (stream is RPCPipeStream)
  262. {
  263. data = new byte[maxCount];
  264. int bytesRead = stream.Read(data, 0, maxCount);
  265. if (bytesRead < maxCount)
  266. {
  267. // EOF, we must trim the response data array
  268. data = ByteReader.ReadBytes(data, 0, bytesRead);
  269. }
  270. return NTStatus.STATUS_SUCCESS;
  271. }
  272. else // File
  273. {
  274. if (stream == null || !stream.CanRead)
  275. {
  276. state.LogToServer(Severity.Debug, "ReadFile: Cannot read '{0}', Invalid Operation.", openFilePath);
  277. return NTStatus.STATUS_ACCESS_DENIED;
  278. }
  279. int bytesRead;
  280. try
  281. {
  282. stream.Seek(offset, SeekOrigin.Begin);
  283. data = new byte[maxCount];
  284. bytesRead = stream.Read(data, 0, maxCount);
  285. }
  286. catch (Exception ex)
  287. {
  288. NTStatus status = ToNTStatus(ex);
  289. state.LogToServer(Severity.Debug, "ReadFile: Cannot read '{0}'. {1}.", openFilePath, status);
  290. return status;
  291. }
  292. if (bytesRead < maxCount)
  293. {
  294. // EOF, we must trim the response data array
  295. data = ByteReader.ReadBytes(data, 0, bytesRead);
  296. }
  297. return NTStatus.STATUS_SUCCESS;
  298. }
  299. }
  300. public static NTStatus WriteFile(out int numberOfBytesWritten, OpenFileObject openFile, long offset, byte[] data, ConnectionState state)
  301. {
  302. numberOfBytesWritten = 0;
  303. string openFilePath = openFile.Path;
  304. Stream stream = openFile.Stream;
  305. if (stream is RPCPipeStream)
  306. {
  307. stream.Write(data, 0, data.Length);
  308. numberOfBytesWritten = data.Length;
  309. return NTStatus.STATUS_SUCCESS;
  310. }
  311. else // File
  312. {
  313. if (stream == null || !stream.CanWrite)
  314. {
  315. state.LogToServer(Severity.Debug, "WriteFile: Cannot write '{0}'. Invalid Operation.", openFilePath);
  316. return NTStatus.STATUS_ACCESS_DENIED;
  317. }
  318. try
  319. {
  320. stream.Seek(offset, SeekOrigin.Begin);
  321. stream.Write(data, 0, data.Length);
  322. }
  323. catch (Exception ex)
  324. {
  325. NTStatus status = ToNTStatus(ex);
  326. state.LogToServer(Severity.Debug, "WriteFile: Cannot write '{0}'. {1}.", openFilePath, status);
  327. return status;
  328. }
  329. numberOfBytesWritten = data.Length;
  330. return NTStatus.STATUS_SUCCESS;
  331. }
  332. }
  333. /// <param name="exception">IFileSystem exception</param>
  334. private static NTStatus ToNTStatus(Exception exception)
  335. {
  336. if (exception is ArgumentException)
  337. {
  338. return NTStatus.STATUS_OBJECT_PATH_SYNTAX_BAD;
  339. }
  340. else if (exception is DirectoryNotFoundException)
  341. {
  342. return NTStatus.STATUS_OBJECT_PATH_NOT_FOUND;
  343. }
  344. else if (exception is FileNotFoundException)
  345. {
  346. return NTStatus.STATUS_OBJECT_PATH_NOT_FOUND;
  347. }
  348. else if (exception is IOException)
  349. {
  350. ushort errorCode = IOExceptionHelper.GetWin32ErrorCode((IOException)exception);
  351. if (errorCode == (ushort)Win32Error.ERROR_SHARING_VIOLATION)
  352. {
  353. return NTStatus.STATUS_SHARING_VIOLATION;
  354. }
  355. else if (errorCode == (ushort)Win32Error.ERROR_DISK_FULL)
  356. {
  357. return NTStatus.STATUS_DISK_FULL;
  358. }
  359. else if (errorCode == (ushort)Win32Error.ERROR_ALREADY_EXISTS)
  360. {
  361. return NTStatus.STATUS_OBJECT_NAME_EXISTS;
  362. }
  363. else
  364. {
  365. return NTStatus.STATUS_DATA_ERROR;
  366. }
  367. }
  368. else if (exception is UnauthorizedAccessException)
  369. {
  370. return NTStatus.STATUS_ACCESS_DENIED;
  371. }
  372. else
  373. {
  374. return NTStatus.STATUS_DATA_ERROR;
  375. }
  376. }
  377. /// <summary>
  378. /// Will return a virtual allocation size, assuming 4096 bytes per cluster
  379. /// </summary>
  380. public static ulong GetAllocationSize(ulong size)
  381. {
  382. return (ulong)Math.Ceiling((double)size / ClusterSize) * ClusterSize;
  383. }
  384. public static string GetShortName(string fileName)
  385. {
  386. string fileNameWithoutExt = System.IO.Path.GetFileNameWithoutExtension(fileName);
  387. string extension = System.IO.Path.GetExtension(fileName);
  388. if (fileNameWithoutExt.Length > 8 || extension.Length > 4)
  389. {
  390. if (fileNameWithoutExt.Length > 8)
  391. {
  392. fileNameWithoutExt = fileNameWithoutExt.Substring(0, 8);
  393. }
  394. if (extension.Length > 4)
  395. {
  396. extension = extension.Substring(0, 4);
  397. }
  398. return fileNameWithoutExt + extension;
  399. }
  400. else
  401. {
  402. return fileName;
  403. }
  404. }
  405. }
  406. }