NTFileSystemHelper.cs 17 KB

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