NTFileSystemHelper.cs 16 KB

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