NTTransactHelper.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. /* Copyright (C) 2014-2019 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.SMB1;
  11. using Utilities;
  12. namespace SMBLibrary.Server.SMB1
  13. {
  14. internal class NTTransactHelper
  15. {
  16. /// <summary>
  17. /// The client MUST send as many secondary requests as are needed to complete the transfer of the transaction request.
  18. /// </summary>
  19. internal static List<SMB1Command> GetNTTransactResponse(SMB1Header header, NTTransactRequest request, ISMBShare share, SMB1ConnectionState state)
  20. {
  21. if (request.TransParameters.Length < request.TotalParameterCount ||
  22. request.TransData.Length < request.TotalDataCount)
  23. {
  24. // A secondary transaction request is pending
  25. ProcessStateObject processState = state.CreateProcessState(header.PID);
  26. processState.SubcommandID = (ushort)request.Function;
  27. processState.MaxParameterCount = request.MaxParameterCount;
  28. processState.MaxDataCount = request.MaxDataCount;
  29. processState.TransactionSetup = request.Setup;
  30. processState.TransactionParameters = new byte[request.TotalParameterCount];
  31. processState.TransactionData = new byte[request.TotalDataCount];
  32. ByteWriter.WriteBytes(processState.TransactionParameters, 0, request.TransParameters);
  33. ByteWriter.WriteBytes(processState.TransactionData, 0, request.TransData);
  34. processState.TransactionParametersReceived += request.TransParameters.Length;
  35. processState.TransactionDataReceived += request.TransData.Length;
  36. return new NTTransactInterimResponse();
  37. }
  38. else
  39. {
  40. // We have a complete command
  41. return GetCompleteNTTransactResponse(header, request.MaxParameterCount, request.MaxDataCount, request.Function, request.Setup, request.TransParameters, request.TransData, share, state);
  42. }
  43. }
  44. /// <summary>
  45. /// There are no secondary response messages.
  46. /// The client MUST send as many secondary requests as are needed to complete the transfer of the transaction request.
  47. /// </summary>
  48. internal static List<SMB1Command> GetNTTransactResponse(SMB1Header header, NTTransactSecondaryRequest request, ISMBShare share, SMB1ConnectionState state)
  49. {
  50. ProcessStateObject processState = state.GetProcessState(header.PID);
  51. if (processState == null)
  52. {
  53. throw new InvalidDataException();
  54. }
  55. ByteWriter.WriteBytes(processState.TransactionParameters, (int)request.ParameterDisplacement, request.TransParameters);
  56. ByteWriter.WriteBytes(processState.TransactionData, (int)request.DataDisplacement, request.TransData);
  57. processState.TransactionParametersReceived += request.TransParameters.Length;
  58. processState.TransactionDataReceived += request.TransData.Length;
  59. if (processState.TransactionParametersReceived < processState.TransactionParameters.Length ||
  60. processState.TransactionDataReceived < processState.TransactionData.Length)
  61. {
  62. return new List<SMB1Command>();
  63. }
  64. else
  65. {
  66. // We have a complete command
  67. state.RemoveProcessState(header.PID);
  68. return GetCompleteNTTransactResponse(header, processState.MaxParameterCount, processState.MaxDataCount, (NTTransactSubcommandName)processState.SubcommandID, processState.TransactionSetup, processState.TransactionParameters, processState.TransactionData, share, state);
  69. }
  70. }
  71. internal static List<SMB1Command> GetCompleteNTTransactResponse(SMB1Header header, uint maxParameterCount, uint maxDataCount, NTTransactSubcommandName subcommandName, byte[] requestSetup, byte[] requestParameters, byte[] requestData, ISMBShare share, SMB1ConnectionState state)
  72. {
  73. NTTransactSubcommand subcommand;
  74. try
  75. {
  76. subcommand = NTTransactSubcommand.GetSubcommandRequest(subcommandName, requestSetup, requestParameters, requestData, header.UnicodeFlag);
  77. }
  78. catch
  79. {
  80. // [MS-CIFS] If the Function code is not defined, the server MUST return STATUS_INVALID_SMB.
  81. header.Status = NTStatus.STATUS_INVALID_SMB;
  82. return new ErrorResponse(CommandName.SMB_COM_NT_TRANSACT);
  83. }
  84. state.LogToServer(Severity.Verbose, "Received complete SMB_COM_NT_TRANSACT subcommand: {0}", subcommand.SubcommandName);
  85. NTTransactSubcommand subcommandResponse = null;
  86. if (subcommand is NTTransactCreateRequest)
  87. {
  88. header.Status = NTStatus.STATUS_NOT_IMPLEMENTED;
  89. }
  90. else if (subcommand is NTTransactIOCTLRequest)
  91. {
  92. subcommandResponse = GetSubcommandResponse(header, maxDataCount, (NTTransactIOCTLRequest)subcommand, share, state);
  93. }
  94. else if (subcommand is NTTransactSetSecurityDescriptorRequest)
  95. {
  96. subcommandResponse = GetSubcommandResponse(header, (NTTransactSetSecurityDescriptorRequest)subcommand, share, state);
  97. }
  98. else if (subcommand is NTTransactNotifyChangeRequest)
  99. {
  100. NotifyChangeHelper.ProcessNTTransactNotifyChangeRequest(header, maxParameterCount, (NTTransactNotifyChangeRequest)subcommand, share, state);
  101. if (header.Status == NTStatus.STATUS_PENDING)
  102. {
  103. return new List<SMB1Command>();
  104. }
  105. }
  106. else if (subcommand is NTTransactQuerySecurityDescriptorRequest)
  107. {
  108. subcommandResponse = GetSubcommandResponse(header, maxDataCount, (NTTransactQuerySecurityDescriptorRequest)subcommand, share, state);
  109. }
  110. else
  111. {
  112. // [MS-CIFS] If the Function code is defined but not implemented, the server MUST return STATUS_SMB_BAD_COMMAND.
  113. header.Status = NTStatus.STATUS_SMB_BAD_COMMAND;
  114. }
  115. if (header.Status != NTStatus.STATUS_SUCCESS && (header.Status != NTStatus.STATUS_BUFFER_OVERFLOW || subcommandResponse == null))
  116. {
  117. return new ErrorResponse(CommandName.SMB_COM_NT_TRANSACT);
  118. }
  119. byte[] responseSetup = subcommandResponse.GetSetup();
  120. byte[] responseParameters = subcommandResponse.GetParameters(header.UnicodeFlag);
  121. byte[] responseData = subcommandResponse.GetData();
  122. return GetNTTransactResponse(responseSetup, responseParameters, responseData, state.MaxBufferSize);
  123. }
  124. private static NTTransactIOCTLResponse GetSubcommandResponse(SMB1Header header, uint maxDataCount, NTTransactIOCTLRequest subcommand, ISMBShare share, SMB1ConnectionState state)
  125. {
  126. SMB1Session session = state.GetSession(header.UID);
  127. string ctlCode = Enum.IsDefined(typeof(IoControlCode), subcommand.FunctionCode) ? ((IoControlCode)subcommand.FunctionCode).ToString() : ("0x" + subcommand.FunctionCode.ToString("X8"));
  128. if (!subcommand.IsFsctl)
  129. {
  130. // [MS-SMB] If the IsFsctl field is set to zero, the server SHOULD fail the request with STATUS_NOT_SUPPORTED
  131. state.LogToServer(Severity.Verbose, "IOCTL: Non-FSCTL requests are not supported. CTL Code: {0}", ctlCode);
  132. header.Status = NTStatus.STATUS_NOT_SUPPORTED;
  133. return null;
  134. }
  135. OpenFileObject openFile = session.GetOpenFileObject(subcommand.FID);
  136. if (openFile == null)
  137. {
  138. state.LogToServer(Severity.Verbose, "IOCTL failed. CTL Code: {0}. Invalid FID. (UID: {1}, TID: {2}, FID: {3})", ctlCode, header.UID, header.TID, subcommand.FID);
  139. header.Status = NTStatus.STATUS_INVALID_HANDLE;
  140. return null;
  141. }
  142. int maxOutputLength = (int)maxDataCount;
  143. byte[] output;
  144. header.Status = share.FileStore.DeviceIOControl(openFile.Handle, subcommand.FunctionCode, subcommand.Data, out output, maxOutputLength);
  145. if (header.Status != NTStatus.STATUS_SUCCESS && header.Status != NTStatus.STATUS_BUFFER_OVERFLOW)
  146. {
  147. state.LogToServer(Severity.Verbose, "IOCTL failed. CTL Code: {0}. NTStatus: {1}. (FID: {2})", ctlCode, header.Status, subcommand.FID);
  148. return null;
  149. }
  150. state.LogToServer(Severity.Verbose, "IOCTL succeeded. CTL Code: {0}. (FID: {1})", ctlCode, subcommand.FID);
  151. NTTransactIOCTLResponse response = new NTTransactIOCTLResponse();
  152. response.Data = output;
  153. return response;
  154. }
  155. private static NTTransactSetSecurityDescriptorResponse GetSubcommandResponse(SMB1Header header, NTTransactSetSecurityDescriptorRequest subcommand, ISMBShare share, SMB1ConnectionState state)
  156. {
  157. SMB1Session session = state.GetSession(header.UID);
  158. OpenFileObject openFile = session.GetOpenFileObject(subcommand.FID);
  159. if (openFile == null)
  160. {
  161. state.LogToServer(Severity.Verbose, "SetSecurityInformation failed. Invalid FID. (UID: {0}, TID: {1}, FID: {2})", header.UID, header.TID, subcommand.FID);
  162. header.Status = NTStatus.STATUS_INVALID_HANDLE;
  163. return null;
  164. }
  165. header.Status = share.FileStore.SetSecurityInformation(openFile.Handle, subcommand.SecurityInformation, subcommand.SecurityDescriptor);
  166. if (header.Status != NTStatus.STATUS_SUCCESS)
  167. {
  168. state.LogToServer(Severity.Verbose, "SetSecurityInformation on '{0}{1}' failed. Security information: 0x{2}, NTStatus: {3}. (FID: {4})", share.Name, openFile.Path, subcommand.SecurityInformation.ToString("X"), header.Status, subcommand.FID);
  169. return null;
  170. }
  171. state.LogToServer(Severity.Verbose, "SetSecurityInformation on '{0}{1}' succeeded. Security information: 0x{2}. (FID: {3})", share.Name, openFile.Path, subcommand.SecurityInformation.ToString("X"), subcommand.FID);
  172. NTTransactSetSecurityDescriptorResponse response = new NTTransactSetSecurityDescriptorResponse();
  173. return response;
  174. }
  175. private static NTTransactQuerySecurityDescriptorResponse GetSubcommandResponse(SMB1Header header, uint maxDataCount, NTTransactQuerySecurityDescriptorRequest subcommand, ISMBShare share, SMB1ConnectionState state)
  176. {
  177. SMB1Session session = state.GetSession(header.UID);
  178. OpenFileObject openFile = session.GetOpenFileObject(subcommand.FID);
  179. if (openFile == null)
  180. {
  181. state.LogToServer(Severity.Verbose, "GetSecurityInformation failed. Invalid FID. (UID: {0}, TID: {1}, FID: {2})", header.UID, header.TID, subcommand.FID);
  182. header.Status = NTStatus.STATUS_INVALID_HANDLE;
  183. return null;
  184. }
  185. int maxOutputLength = (int)maxDataCount;
  186. SecurityDescriptor securityDescriptor;
  187. header.Status = share.FileStore.GetSecurityInformation(out securityDescriptor, openFile.Handle, subcommand.SecurityInfoFields);
  188. if (header.Status != NTStatus.STATUS_SUCCESS)
  189. {
  190. state.LogToServer(Severity.Verbose, "GetSecurityInformation on '{0}{1}' failed. Security information: 0x{2}, NTStatus: {3}. (FID: {4})", share.Name, openFile.Path, subcommand.SecurityInfoFields.ToString("X"), header.Status, subcommand.FID);
  191. return null;
  192. }
  193. NTTransactQuerySecurityDescriptorResponse response = new NTTransactQuerySecurityDescriptorResponse();
  194. response.LengthNeeded = (uint)securityDescriptor.Length;
  195. if (response.LengthNeeded <= maxDataCount)
  196. {
  197. state.LogToServer(Severity.Verbose, "GetSecurityInformation on '{0}{1}' succeeded. Security information: 0x{2}. (FID: {3})", share.Name, openFile.Path, subcommand.SecurityInfoFields.ToString("X"), subcommand.FID);
  198. response.SecurityDescriptor = securityDescriptor;
  199. }
  200. else
  201. {
  202. state.LogToServer(Severity.Verbose, "GetSecurityInformation on '{0}{1}' failed. Security information: 0x{2}, NTStatus: STATUS_BUFFER_TOO_SMALL. (FID: {3})", share.Name, openFile.Path, subcommand.SecurityInfoFields.ToString("X"), subcommand.FID);
  203. header.Status = NTStatus.STATUS_BUFFER_TOO_SMALL;
  204. }
  205. return response;
  206. }
  207. internal static List<SMB1Command> GetNTTransactResponse(byte[] responseSetup, byte[] responseParameters, byte[] responseData, int maxBufferSize)
  208. {
  209. List<SMB1Command> result = new List<SMB1Command>();
  210. NTTransactResponse response = new NTTransactResponse();
  211. result.Add(response);
  212. int responseSize = NTTransactResponse.CalculateMessageSize(responseSetup.Length, responseParameters.Length, responseData.Length);
  213. if (responseSize <= maxBufferSize)
  214. {
  215. response.Setup = responseSetup;
  216. response.TotalParameterCount = (ushort)responseParameters.Length;
  217. response.TotalDataCount = (ushort)responseData.Length;
  218. response.TransParameters = responseParameters;
  219. response.TransData = responseData;
  220. }
  221. else
  222. {
  223. int currentDataLength = maxBufferSize - (responseSize - responseData.Length);
  224. byte[] buffer = new byte[currentDataLength];
  225. Array.Copy(responseData, 0, buffer, 0, currentDataLength);
  226. response.Setup = responseSetup;
  227. response.TotalParameterCount = (ushort)responseParameters.Length;
  228. response.TotalDataCount = (ushort)responseData.Length;
  229. response.TransParameters = responseParameters;
  230. response.TransData = buffer;
  231. int dataBytesLeftToSend = responseData.Length - currentDataLength;
  232. while (dataBytesLeftToSend > 0)
  233. {
  234. NTTransactResponse additionalResponse = new NTTransactResponse();
  235. currentDataLength = dataBytesLeftToSend;
  236. responseSize = TransactionResponse.CalculateMessageSize(0, 0, dataBytesLeftToSend);
  237. if (responseSize > maxBufferSize)
  238. {
  239. currentDataLength = maxBufferSize - (responseSize - dataBytesLeftToSend);
  240. }
  241. buffer = new byte[currentDataLength];
  242. int dataBytesSent = responseData.Length - dataBytesLeftToSend;
  243. Array.Copy(responseData, dataBytesSent, buffer, 0, currentDataLength);
  244. additionalResponse.TotalParameterCount = (ushort)responseParameters.Length;
  245. additionalResponse.TotalDataCount = (ushort)responseData.Length;
  246. additionalResponse.TransData = buffer;
  247. additionalResponse.ParameterDisplacement = (ushort)response.TransParameters.Length;
  248. additionalResponse.DataDisplacement = (ushort)dataBytesSent;
  249. result.Add(additionalResponse);
  250. dataBytesLeftToSend -= currentDataLength;
  251. }
  252. }
  253. return result;
  254. }
  255. }
  256. }