SMBServer.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  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.Net;
  11. using System.Net.Sockets;
  12. using System.Text;
  13. using SMBLibrary.NetBios;
  14. using SMBLibrary.SMB1;
  15. using SMBLibrary.Services;
  16. using Utilities;
  17. namespace SMBLibrary.Server
  18. {
  19. public class SMBServer
  20. {
  21. public const int NetBiosOverTCPPort = 139;
  22. public const int DirectTCPPort = 445;
  23. public const string NTLanManagerDialect = "NT LM 0.12";
  24. public const bool EnableExtendedSecurity = true;
  25. private ShareCollection m_shares; // e.g. Shared folders
  26. private INTLMAuthenticationProvider m_users;
  27. private NamedPipeShare m_services; // Named pipes
  28. private IPAddress m_serverAddress;
  29. private SMBTransportType m_transport;
  30. private Socket m_listenerSocket;
  31. private bool m_listening;
  32. private Guid m_serverGuid;
  33. public SMBServer(ShareCollection shares, INTLMAuthenticationProvider users, IPAddress serverAddress, SMBTransportType transport)
  34. {
  35. m_shares = shares;
  36. m_users = users;
  37. m_serverAddress = serverAddress;
  38. m_serverGuid = Guid.NewGuid();
  39. m_transport = transport;
  40. m_services = new NamedPipeShare(shares.ListShares());
  41. }
  42. public void Start()
  43. {
  44. if (!m_listening)
  45. {
  46. m_listening = true;
  47. m_listenerSocket = new Socket(m_serverAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  48. int port = (m_transport == SMBTransportType.DirectTCPTransport ? DirectTCPPort : NetBiosOverTCPPort);
  49. m_listenerSocket.Bind(new IPEndPoint(m_serverAddress, port));
  50. m_listenerSocket.Listen((int)SocketOptionName.MaxConnections);
  51. m_listenerSocket.BeginAccept(ConnectRequestCallback, m_listenerSocket);
  52. }
  53. }
  54. public void Stop()
  55. {
  56. m_listening = false;
  57. SocketUtils.ReleaseSocket(m_listenerSocket);
  58. }
  59. // This method Accepts new connections
  60. private void ConnectRequestCallback(IAsyncResult ar)
  61. {
  62. System.Diagnostics.Debug.Print("[{0}] New connection request", DateTime.Now.ToString("HH:mm:ss:ffff"));
  63. Socket listenerSocket = (Socket)ar.AsyncState;
  64. Socket clientSocket;
  65. try
  66. {
  67. clientSocket = listenerSocket.EndAccept(ar);
  68. }
  69. catch (ObjectDisposedException)
  70. {
  71. return;
  72. }
  73. catch (SocketException ex)
  74. {
  75. const int WSAECONNRESET = 10054;
  76. // Client may have closed the connection before we start to process the connection request.
  77. // When we get this error, we have to continue to accept other requests.
  78. // See http://stackoverflow.com/questions/7704417/socket-endaccept-error-10054
  79. if (ex.ErrorCode == WSAECONNRESET)
  80. {
  81. m_listenerSocket.BeginAccept(ConnectRequestCallback, m_listenerSocket);
  82. }
  83. System.Diagnostics.Debug.Print("[{0}] Connection request error {1}", DateTime.Now.ToString("HH:mm:ss:ffff"), ex.ErrorCode);
  84. return;
  85. }
  86. StateObject state = new StateObject();
  87. // Disable the Nagle Algorithm for this tcp socket:
  88. clientSocket.NoDelay = true;
  89. state.ClientSocket = clientSocket;
  90. try
  91. {
  92. clientSocket.BeginReceive(state.ReceiveBuffer.Buffer, state.ReceiveBuffer.WriteOffset, state.ReceiveBuffer.AvailableLength, 0, ReceiveCallback, state);
  93. }
  94. catch (ObjectDisposedException)
  95. {
  96. }
  97. catch (SocketException)
  98. {
  99. }
  100. m_listenerSocket.BeginAccept(ConnectRequestCallback, m_listenerSocket);
  101. }
  102. private void ReceiveCallback(IAsyncResult result)
  103. {
  104. StateObject state = (StateObject)result.AsyncState;
  105. Socket clientSocket = state.ClientSocket;
  106. if (!m_listening)
  107. {
  108. clientSocket.Close();
  109. return;
  110. }
  111. int numberOfBytesReceived;
  112. try
  113. {
  114. numberOfBytesReceived = clientSocket.EndReceive(result);
  115. }
  116. catch (ObjectDisposedException)
  117. {
  118. return;
  119. }
  120. catch (SocketException)
  121. {
  122. return;
  123. }
  124. if (numberOfBytesReceived == 0)
  125. {
  126. // The other side has closed the connection
  127. System.Diagnostics.Debug.Print("[{0}] The other side closed the connection", DateTime.Now.ToString("HH:mm:ss:ffff"));
  128. clientSocket.Close();
  129. return;
  130. }
  131. SMBConnectionReceiveBuffer receiveBuffer = state.ReceiveBuffer;
  132. receiveBuffer.SetNumberOfBytesReceived(numberOfBytesReceived);
  133. ProcessConnectionBuffer(state);
  134. if (clientSocket.Connected)
  135. {
  136. try
  137. {
  138. clientSocket.BeginReceive(state.ReceiveBuffer.Buffer, state.ReceiveBuffer.WriteOffset, state.ReceiveBuffer.AvailableLength, 0, ReceiveCallback, state);
  139. }
  140. catch (ObjectDisposedException)
  141. {
  142. }
  143. catch (SocketException)
  144. {
  145. }
  146. }
  147. }
  148. public void ProcessConnectionBuffer(StateObject state)
  149. {
  150. Socket clientSocket = state.ClientSocket;
  151. SMBConnectionReceiveBuffer receiveBuffer = state.ReceiveBuffer;
  152. while (receiveBuffer.HasCompletePacket())
  153. {
  154. SessionPacket packet = null;
  155. try
  156. {
  157. packet = receiveBuffer.DequeuePacket();
  158. }
  159. catch (Exception)
  160. {
  161. state.ClientSocket.Close();
  162. }
  163. if (packet != null)
  164. {
  165. ProcessPacket(packet, state);
  166. }
  167. }
  168. }
  169. public void ProcessPacket(SessionPacket packet, StateObject state)
  170. {
  171. if (packet is SessionRequestPacket && m_transport == SMBTransportType.NetBiosOverTCP)
  172. {
  173. PositiveSessionResponsePacket response = new PositiveSessionResponsePacket();
  174. TrySendPacket(state, response);
  175. }
  176. else if (packet is SessionKeepAlivePacket && m_transport == SMBTransportType.NetBiosOverTCP)
  177. {
  178. // [RFC 1001] NetBIOS session keep alives do not require a response from the NetBIOS peer
  179. }
  180. else if (packet is SessionMessagePacket)
  181. {
  182. SMB1Message message = null;
  183. #if DEBUG
  184. message = SMB1Message.GetSMBMessage(packet.Trailer);
  185. System.Diagnostics.Debug.Print("[{0}] Message Received: {1} Commands, First Command: {2}, Packet length: {3}", DateTime.Now.ToString("HH:mm:ss:ffff"), message.Commands.Count, message.Commands[0].CommandName.ToString(), packet.Length);
  186. #else
  187. try
  188. {
  189. message = SMB1Message.GetSMBMessage(packet.Trailer);
  190. }
  191. catch (Exception)
  192. {
  193. state.ClientSocket.Close();
  194. return;
  195. }
  196. #endif
  197. ProcessMessage(message, state);
  198. }
  199. else
  200. {
  201. System.Diagnostics.Debug.Print("[{0}] Invalid NetBIOS packet", DateTime.Now.ToString("HH:mm:ss:ffff"));
  202. state.ClientSocket.Close();
  203. return;
  204. }
  205. }
  206. public void ProcessMessage(SMB1Message message, StateObject state)
  207. {
  208. SMB1Message reply = new SMB1Message();
  209. PrepareResponseHeader(reply, message);
  210. List<SMB1Command> sendQueue = new List<SMB1Command>();
  211. foreach (SMB1Command command in message.Commands)
  212. {
  213. SMB1Command response = ProcessCommand(reply.Header, command, state, sendQueue);
  214. if (response != null)
  215. {
  216. reply.Commands.Add(response);
  217. }
  218. if (reply.Header.Status != NTStatus.STATUS_SUCCESS)
  219. {
  220. break;
  221. }
  222. }
  223. if (reply.Commands.Count > 0)
  224. {
  225. TrySendMessage(state, reply);
  226. foreach (SMB1Command command in sendQueue)
  227. {
  228. SMB1Message secondaryReply = new SMB1Message();
  229. secondaryReply.Header = reply.Header;
  230. secondaryReply.Commands.Add(command);
  231. TrySendMessage(state, secondaryReply);
  232. }
  233. }
  234. }
  235. /// <summary>
  236. /// May return null
  237. /// </summary>
  238. public SMB1Command ProcessCommand(SMB1Header header, SMB1Command command, StateObject state, List<SMB1Command> sendQueue)
  239. {
  240. if (command is NegotiateRequest)
  241. {
  242. NegotiateRequest request = (NegotiateRequest)command;
  243. if (request.Dialects.Contains(SMBServer.NTLanManagerDialect))
  244. {
  245. if (EnableExtendedSecurity && header.ExtendedSecurityFlag)
  246. {
  247. return NegotiateHelper.GetNegotiateResponseExtended(request, m_serverGuid);
  248. }
  249. else
  250. {
  251. byte[] serverChallenge = m_users.GenerateServerChallenge();
  252. return NegotiateHelper.GetNegotiateResponse(header, request, serverChallenge);
  253. }
  254. }
  255. else
  256. {
  257. return new NegotiateResponseNotSupported();
  258. }
  259. }
  260. else if (command is SessionSetupAndXRequest)
  261. {
  262. SessionSetupAndXRequest request = (SessionSetupAndXRequest)command;
  263. state.MaxBufferSize = request.MaxBufferSize;
  264. return NegotiateHelper.GetSessionSetupResponse(header, request, m_users, state);
  265. }
  266. else if (command is SessionSetupAndXRequestExtended)
  267. {
  268. SessionSetupAndXRequestExtended request = (SessionSetupAndXRequestExtended)command;
  269. state.MaxBufferSize = request.MaxBufferSize;
  270. return NegotiateHelper.GetSessionSetupResponseExtended(header, request, m_users, state);
  271. }
  272. else if (command is EchoRequest)
  273. {
  274. return ServerResponseHelper.GetEchoResponse((EchoRequest)command, sendQueue);
  275. }
  276. else if (state.IsAuthenticated(header.UID))
  277. {
  278. if (command is TreeConnectAndXRequest)
  279. {
  280. TreeConnectAndXRequest request = (TreeConnectAndXRequest)command;
  281. return TreeConnectHelper.GetTreeConnectResponse(header, request, state, m_shares);
  282. }
  283. else if (command is LogoffAndXRequest)
  284. {
  285. return new LogoffAndXResponse();
  286. }
  287. else if (state.IsTreeConnected(header.TID))
  288. {
  289. string rootPath = state.GetConnectedTreePath(header.TID);
  290. ISMBShare share;
  291. if (state.IsIPC(header.TID))
  292. {
  293. share = m_services;
  294. }
  295. else
  296. {
  297. share = m_shares.GetShareFromRelativePath(rootPath);
  298. }
  299. if (command is CreateDirectoryRequest)
  300. {
  301. if (!(share is FileSystemShare))
  302. {
  303. header.Status = NTStatus.STATUS_SMB_BAD_COMMAND;
  304. return new ErrorResponse(command.CommandName);
  305. }
  306. CreateDirectoryRequest request = (CreateDirectoryRequest)command;
  307. return FileSystemResponseHelper.GetCreateDirectoryResponse(header, request, (FileSystemShare)share, state);
  308. }
  309. else if (command is DeleteDirectoryRequest)
  310. {
  311. if (!(share is FileSystemShare))
  312. {
  313. header.Status = NTStatus.STATUS_SMB_BAD_COMMAND;
  314. return new ErrorResponse(command.CommandName);
  315. }
  316. DeleteDirectoryRequest request = (DeleteDirectoryRequest)command;
  317. return FileSystemResponseHelper.GetDeleteDirectoryResponse(header, request, (FileSystemShare)share, state);
  318. }
  319. else if (command is CloseRequest)
  320. {
  321. CloseRequest request = (CloseRequest)command;
  322. return ServerResponseHelper.GetCloseResponse(header, request, share, state);
  323. }
  324. else if (command is FlushRequest)
  325. {
  326. return new FlushResponse();
  327. }
  328. else if (command is DeleteRequest)
  329. {
  330. if (!(share is FileSystemShare))
  331. {
  332. header.Status = NTStatus.STATUS_SMB_BAD_COMMAND;
  333. return new ErrorResponse(command.CommandName);
  334. }
  335. DeleteRequest request = (DeleteRequest)command;
  336. return FileSystemResponseHelper.GetDeleteResponse(header, request, (FileSystemShare)share, state);
  337. }
  338. else if (command is RenameRequest)
  339. {
  340. if (!(share is FileSystemShare))
  341. {
  342. header.Status = NTStatus.STATUS_SMB_BAD_COMMAND;
  343. return new ErrorResponse(command.CommandName);
  344. }
  345. RenameRequest request = (RenameRequest)command;
  346. return FileSystemResponseHelper.GetRenameResponse(header, request, (FileSystemShare)share, state);
  347. }
  348. else if (command is QueryInformationRequest)
  349. {
  350. if (!(share is FileSystemShare))
  351. {
  352. header.Status = NTStatus.STATUS_SMB_BAD_COMMAND;
  353. return new ErrorResponse(command.CommandName);
  354. }
  355. QueryInformationRequest request = (QueryInformationRequest)command;
  356. return FileSystemResponseHelper.GetQueryInformationResponse(header, request, (FileSystemShare)share);
  357. }
  358. else if (command is SetInformationRequest)
  359. {
  360. if (!(share is FileSystemShare))
  361. {
  362. header.Status = NTStatus.STATUS_SMB_BAD_COMMAND;
  363. return new ErrorResponse(command.CommandName);
  364. }
  365. SetInformationRequest request = (SetInformationRequest)command;
  366. return FileSystemResponseHelper.GetSetInformationResponse(header, request, (FileSystemShare)share, state);
  367. }
  368. else if (command is ReadRequest)
  369. {
  370. ReadRequest request = (ReadRequest)command;
  371. return ReadWriteResponseHelper.GetReadResponse(header, request, share, state);
  372. }
  373. else if (command is WriteRequest)
  374. {
  375. string userName = state.GetConnectedUserName(header.UID);
  376. if (share is FileSystemShare && !((FileSystemShare)share).HasWriteAccess(userName))
  377. {
  378. header.Status = NTStatus.STATUS_ACCESS_DENIED;
  379. return new ErrorResponse(command.CommandName);
  380. }
  381. WriteRequest request = (WriteRequest)command;
  382. return ReadWriteResponseHelper.GetWriteResponse(header, request, share, state);
  383. }
  384. else if (command is CheckDirectoryRequest)
  385. {
  386. if (!(share is FileSystemShare))
  387. {
  388. header.Status = NTStatus.STATUS_SMB_BAD_COMMAND;
  389. return new ErrorResponse(command.CommandName);
  390. }
  391. CheckDirectoryRequest request = (CheckDirectoryRequest)command;
  392. return FileSystemResponseHelper.GetCheckDirectoryResponse(header, request, (FileSystemShare)share);
  393. }
  394. else if (command is WriteRawRequest)
  395. {
  396. // [MS-CIFS] 3.3.5.26 - Receiving an SMB_COM_WRITE_RAW Request:
  397. // the server MUST verify that the Server.Capabilities include CAP_RAW_MODE,
  398. // If an error is detected [..] the Write Raw operation MUST fail and
  399. // the server MUST return a Final Server Response [..] with the Count field set to zero.
  400. return new WriteRawFinalResponse();
  401. }
  402. else if (command is SetInformation2Request)
  403. {
  404. if (!(share is FileSystemShare))
  405. {
  406. header.Status = NTStatus.STATUS_SMB_BAD_COMMAND;
  407. return new ErrorResponse(command.CommandName);
  408. }
  409. SetInformation2Request request = (SetInformation2Request)command;
  410. return FileSystemResponseHelper.GetSetInformation2Response(header, request, (FileSystemShare)share, state);
  411. }
  412. else if (command is LockingAndXRequest)
  413. {
  414. header.Status = NTStatus.STATUS_ACCESS_DENIED;
  415. return new ErrorResponse(CommandName.SMB_COM_LOCKING_ANDX);
  416. }
  417. else if (command is OpenAndXRequest)
  418. {
  419. OpenAndXRequest request = (OpenAndXRequest)command;
  420. return OpenAndXHelper.GetOpenAndXResponse(header, request, share, state);
  421. }
  422. else if (command is ReadAndXRequest)
  423. {
  424. ReadAndXRequest request = (ReadAndXRequest)command;
  425. return ReadWriteResponseHelper.GetReadResponse(header, request, share, state);
  426. }
  427. else if (command is WriteAndXRequest)
  428. {
  429. string userName = state.GetConnectedUserName(header.UID);
  430. if (share is FileSystemShare && !((FileSystemShare)share).HasWriteAccess(userName))
  431. {
  432. header.Status = NTStatus.STATUS_ACCESS_DENIED;
  433. return new ErrorResponse(command.CommandName);
  434. }
  435. WriteAndXRequest request = (WriteAndXRequest)command;
  436. return ReadWriteResponseHelper.GetWriteResponse(header, request, share, state);
  437. }
  438. else if (command is FindClose2Request)
  439. {
  440. return ServerResponseHelper.GetFindClose2Request(header, (FindClose2Request)command, state);
  441. }
  442. else if (command is TreeDisconnectRequest)
  443. {
  444. TreeDisconnectRequest request = (TreeDisconnectRequest)command;
  445. return TreeConnectHelper.GetTreeDisconnectResponse(header, request, state);
  446. }
  447. else if (command is TransactionRequest) // Both TransactionRequest and Transaction2Request
  448. {
  449. TransactionRequest request = (TransactionRequest)command;
  450. try
  451. {
  452. return TransactionHelper.GetTransactionResponse(header, request, share, state, sendQueue);
  453. }
  454. catch (UnsupportedInformationLevelException)
  455. {
  456. header.Status = NTStatus.STATUS_INVALID_PARAMETER;
  457. return new ErrorResponse(command.CommandName);
  458. }
  459. }
  460. else if (command is TransactionSecondaryRequest) // Both TransactionSecondaryRequest and Transaction2SecondaryRequest
  461. {
  462. TransactionSecondaryRequest request = (TransactionSecondaryRequest)command;
  463. try
  464. {
  465. return TransactionHelper.GetTransactionResponse(header, request, share, state, sendQueue);
  466. }
  467. catch (UnsupportedInformationLevelException)
  468. {
  469. header.Status = NTStatus.STATUS_INVALID_PARAMETER;
  470. return new ErrorResponse(command.CommandName);
  471. }
  472. }
  473. else if (command is NTTransactRequest)
  474. {
  475. NTTransactRequest request = (NTTransactRequest)command;
  476. return NTTransactHelper.GetNTTransactResponse(header, request, share, state, sendQueue);
  477. }
  478. else if (command is NTTransactSecondaryRequest)
  479. {
  480. NTTransactSecondaryRequest request = (NTTransactSecondaryRequest)command;
  481. return NTTransactHelper.GetNTTransactResponse(header, request, share, state, sendQueue);
  482. }
  483. else if (command is NTCreateAndXRequest)
  484. {
  485. NTCreateAndXRequest request = (NTCreateAndXRequest)command;
  486. return NTCreateHelper.GetNTCreateResponse(header, request, share, state);
  487. }
  488. }
  489. else
  490. {
  491. header.Status = NTStatus.STATUS_SMB_BAD_TID;
  492. return new ErrorResponse(command.CommandName);
  493. }
  494. }
  495. header.Status = NTStatus.STATUS_SMB_BAD_COMMAND;
  496. return new ErrorResponse(command.CommandName);
  497. }
  498. public static void TrySendMessage(StateObject state, SMB1Message reply)
  499. {
  500. SessionMessagePacket packet = new SessionMessagePacket();
  501. packet.Trailer = reply.GetBytes();
  502. TrySendPacket(state, packet);
  503. System.Diagnostics.Debug.Print("[{0}] Reply sent: {1} Commands, First Command: {2}, Packet length: {3}", DateTime.Now.ToString("HH:mm:ss:ffff"), reply.Commands.Count, reply.Commands[0].CommandName.ToString(), packet.Length);
  504. }
  505. public static void TrySendPacket(StateObject state, SessionPacket response)
  506. {
  507. Socket clientSocket = state.ClientSocket;
  508. try
  509. {
  510. clientSocket.Send(response.GetBytes());
  511. }
  512. catch (SocketException)
  513. {
  514. }
  515. catch (ObjectDisposedException)
  516. {
  517. }
  518. }
  519. private static void PrepareResponseHeader(SMB1Message response, SMB1Message request)
  520. {
  521. response.Header.Status = NTStatus.STATUS_SUCCESS;
  522. response.Header.Flags = HeaderFlags.CaseInsensitive | HeaderFlags.CanonicalizedPaths | HeaderFlags.Reply;
  523. response.Header.Flags2 = HeaderFlags2.NTStatusCode;
  524. if ((request.Header.Flags2 & HeaderFlags2.LongNamesAllowed) > 0)
  525. {
  526. response.Header.Flags2 |= HeaderFlags2.LongNamesAllowed | HeaderFlags2.LongNameUsed;
  527. }
  528. if ((request.Header.Flags2 & HeaderFlags2.ExtendedAttributes) > 0)
  529. {
  530. response.Header.Flags2 |= HeaderFlags2.ExtendedAttributes;
  531. }
  532. if ((request.Header.Flags2 & HeaderFlags2.ExtendedSecurity) > 0)
  533. {
  534. response.Header.Flags2 |= HeaderFlags2.ExtendedSecurity;
  535. }
  536. if ((request.Header.Flags2 & HeaderFlags2.Unicode) > 0)
  537. {
  538. response.Header.Flags2 |= HeaderFlags2.Unicode;
  539. }
  540. response.Header.MID = request.Header.MID;
  541. response.Header.PID = request.Header.PID;
  542. response.Header.UID = request.Header.UID;
  543. response.Header.TID = request.Header.TID;
  544. }
  545. }
  546. }