From 211adcc78a7b77705837b4abecbb90eb5d42ec2d Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 14 Jan 2017 00:56:35 +0200 Subject: [PATCH] Added RPCPipeStream to improve read / write abstraction --- SMBLibrary/SMBLibrary.csproj | 1 + .../ConnectionState/SMB1ConnectionState.cs | 21 ---- SMBLibrary/Server/SMB1/NTCreateHelper.cs | 6 +- SMBLibrary/Server/SMB1/OpenAndXHelper.cs | 6 +- .../Server/SMB1/ReadWriteResponseHelper.cs | 32 ++--- .../SMB1/TransactionSubcommandHelper.cs | 21 +--- SMBLibrary/Server/Shares/NamedPipeShare.cs | 18 ++- SMBLibrary/Services/RPCPipeStream.cs | 116 ++++++++++++++++++ 8 files changed, 157 insertions(+), 64 deletions(-) create mode 100644 SMBLibrary/Services/RPCPipeStream.cs diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 287cf4c..7727ddf 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -139,6 +139,7 @@ + diff --git a/SMBLibrary/Server/ConnectionState/SMB1ConnectionState.cs b/SMBLibrary/Server/ConnectionState/SMB1ConnectionState.cs index 4b1b34c..d3808e8 100644 --- a/SMBLibrary/Server/ConnectionState/SMB1ConnectionState.cs +++ b/SMBLibrary/Server/ConnectionState/SMB1ConnectionState.cs @@ -28,8 +28,6 @@ namespace SMBLibrary.Server // Key is FID private Dictionary m_openedFiles = new Dictionary(); private ushort m_nextFID = 1; - // Key is FID - private Dictionary m_namedPipeResponse = new Dictionary(); // Key is PID public Dictionary ProcessStateList = new Dictionary(); @@ -262,25 +260,6 @@ namespace SMBLibrary.Server m_openedFiles.Remove(fileID); } - public void StoreNamedPipeReply(ushort fileID, byte[] response) - { - m_namedPipeResponse.Add(fileID, response); - } - - public byte[] RetrieveNamedPipeReply(ushort fileID) - { - if (m_namedPipeResponse.ContainsKey(fileID)) - { - byte[] result = m_namedPipeResponse[fileID]; - m_namedPipeResponse.Remove(fileID); - return result; - } - else - { - return new byte[0]; - } - } - public uint? GetMaxDataCount(uint processID) { ProcessStateObject processState = GetProcessState(processID); diff --git a/SMBLibrary/Server/SMB1/NTCreateHelper.cs b/SMBLibrary/Server/SMB1/NTCreateHelper.cs index 7d4167b..5fbecc7 100644 --- a/SMBLibrary/Server/SMB1/NTCreateHelper.cs +++ b/SMBLibrary/Server/SMB1/NTCreateHelper.cs @@ -22,10 +22,10 @@ namespace SMBLibrary.Server.SMB1 string path = request.FileName; if (share is NamedPipeShare) { - RemoteService service = ((NamedPipeShare)share).GetService(path); - if (service != null) + Stream pipeStream = ((NamedPipeShare)share).OpenPipe(path); + if (pipeStream != null) { - ushort? fileID = state.AddOpenedFile(path); + ushort? fileID = state.AddOpenedFile(path, pipeStream); if (!fileID.HasValue) { header.Status = NTStatus.STATUS_TOO_MANY_OPENED_FILES; diff --git a/SMBLibrary/Server/SMB1/OpenAndXHelper.cs b/SMBLibrary/Server/SMB1/OpenAndXHelper.cs index fa1d7dd..daf017f 100644 --- a/SMBLibrary/Server/SMB1/OpenAndXHelper.cs +++ b/SMBLibrary/Server/SMB1/OpenAndXHelper.cs @@ -22,10 +22,10 @@ namespace SMBLibrary.Server.SMB1 string path = request.FileName; if (share is NamedPipeShare) { - RemoteService service = ((NamedPipeShare)share).GetService(path); - if (service != null) + Stream pipeStream = ((NamedPipeShare)share).OpenPipe(path); + if (pipeStream != null) { - ushort? fileID = state.AddOpenedFile(path); + ushort? fileID = state.AddOpenedFile(path, pipeStream); if (!fileID.HasValue) { header.Status = NTStatus.STATUS_TOO_MANY_OPENED_FILES; diff --git a/SMBLibrary/Server/SMB1/ReadWriteResponseHelper.cs b/SMBLibrary/Server/SMB1/ReadWriteResponseHelper.cs index d75a6b5..30df80e 100644 --- a/SMBLibrary/Server/SMB1/ReadWriteResponseHelper.cs +++ b/SMBLibrary/Server/SMB1/ReadWriteResponseHelper.cs @@ -72,16 +72,20 @@ namespace SMBLibrary.Server.SMB1 return null; } string openedFilePath = openedFile.Path; - + Stream stream = openedFile.Stream; if (share is NamedPipeShare) { - return state.RetrieveNamedPipeReply(FID); + byte[] data = new byte[maxCount]; + int bytesRead = stream.Read(data, 0, maxCount); + if (bytesRead < maxCount) + { + // EOF, we must trim the response data array + data = ByteReader.ReadBytes(data, 0, bytesRead); + } + return data; } else // FileSystemShare { - FileSystemShare fileSystemShare = (FileSystemShare)share; - IFileSystem fileSystem = fileSystemShare.FileSystem; - Stream stream = openedFile.Stream; if (stream == null) { @@ -176,26 +180,14 @@ namespace SMBLibrary.Server.SMB1 return 0; } string openedFilePath = openedFile.Path; - + Stream stream = openedFile.Stream; if (share is NamedPipeShare) { - RemoteService service = ((NamedPipeShare)share).GetService(openedFilePath); - if (service != null) - { - RPCPDU rpcRequest = RPCPDU.GetPDU(data, 0); - RPCPDU rpcReply = RemoteServiceHelper.GetRPCReply(rpcRequest, service); - byte[] replyData = rpcReply.GetBytes(); - state.StoreNamedPipeReply(FID, replyData); - return (uint)data.Length; - } - - // This code should not execute unless the SMB request (sequence) is invalid - header.Status = NTStatus.STATUS_INVALID_SMB; - return 0; + stream.Write(data, 0, data.Length); + return (uint)data.Length; } else // FileSystemShare { - Stream stream = openedFile.Stream; if (stream == null) { header.Status = NTStatus.STATUS_ACCESS_DENIED; diff --git a/SMBLibrary/Server/SMB1/TransactionSubcommandHelper.cs b/SMBLibrary/Server/SMB1/TransactionSubcommandHelper.cs index 5fd2660..0a59451 100644 --- a/SMBLibrary/Server/SMB1/TransactionSubcommandHelper.cs +++ b/SMBLibrary/Server/SMB1/TransactionSubcommandHelper.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014 Tal Aloni . All rights reserved. * * You can redistribute this program and/or modify it under the terms of * the GNU Lesser Public License as published by the Free Software Foundation, @@ -18,26 +18,17 @@ namespace SMBLibrary.Server.SMB1 { internal static TransactionTransactNamedPipeResponse GetSubcommandResponse(SMB1Header header, TransactionTransactNamedPipeRequest subcommand, NamedPipeShare share, SMB1ConnectionState state) { - string openedFilePath = state.GetOpenedFilePath(subcommand.FID); - if (openedFilePath == null) + OpenedFileObject openedFile = state.GetOpenedFileObject(subcommand.FID); + if (openedFile == null) { header.Status = NTStatus.STATUS_INVALID_HANDLE; return null; } TransactionTransactNamedPipeResponse response = new TransactionTransactNamedPipeResponse(); - RemoteService service = share.GetService(openedFilePath); - if (service != null) - { - RPCPDU rpcRequest = RPCPDU.GetPDU(subcommand.WriteData, 0); - RPCPDU rpcReply = RemoteServiceHelper.GetRPCReply(rpcRequest, service); - response.ReadData = rpcReply.GetBytes(); - return response; - } - - // This code should not execute unless the request sequence is invalid - header.Status = NTStatus.STATUS_INVALID_SMB; - return null; + openedFile.Stream.Write(subcommand.WriteData, 0, subcommand.WriteData.Length); + response.ReadData = ByteReader.ReadAllBytes(openedFile.Stream); + return response; } } } diff --git a/SMBLibrary/Server/Shares/NamedPipeShare.cs b/SMBLibrary/Server/Shares/NamedPipeShare.cs index 7532d5c..e3bfe42 100644 --- a/SMBLibrary/Server/Shares/NamedPipeShare.cs +++ b/SMBLibrary/Server/Shares/NamedPipeShare.cs @@ -6,7 +6,7 @@ */ using System; using System.Collections.Generic; -using System.Text; +using System.IO; using SMBLibrary.RPC; using SMBLibrary.Services; @@ -23,7 +23,21 @@ namespace SMBLibrary.Server this.Add(new WorkstationService(Environment.MachineName, Environment.MachineName)); } - public RemoteService GetService(string path) + public Stream OpenPipe(string path) + { + // It is possible to have a named pipe that does not use RPC (e.g. MS-WSP), + // However this is not currently needed by our implementation. + RemoteService service = GetService(path); + if (service != null) + { + // All instances of a named pipe share the same pipe name, but each instance has its own buffers and handles, + // and provides a separate conduit for client/server communication. + return new RPCPipeStream(service); + } + return null; + } + + private RemoteService GetService(string path) { foreach (RemoteService service in this) { diff --git a/SMBLibrary/Services/RPCPipeStream.cs b/SMBLibrary/Services/RPCPipeStream.cs new file mode 100644 index 0000000..7b878ed --- /dev/null +++ b/SMBLibrary/Services/RPCPipeStream.cs @@ -0,0 +1,116 @@ +/* Copyright (C) 2017 Tal Aloni . All rights reserved. + * + * You can redistribute this program and/or modify it under the terms of + * the GNU Lesser Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + */ +using System; +using System.Collections.Generic; +using System.IO; +using SMBLibrary.RPC; + +namespace SMBLibrary.Services +{ + public class RPCPipeStream : Stream + { + private RemoteService m_service; + private MemoryStream m_outputStream; + + public RPCPipeStream(RemoteService service) + { + m_service = service; + m_outputStream = new MemoryStream(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return m_outputStream.Read(buffer, offset, count); + } + + public override void Write(byte[] buffer, int offset, int count) + { + int lengthOfPDUs = 0; + do + { + RPCPDU rpcRequest = RPCPDU.GetPDU(buffer, offset); + lengthOfPDUs += rpcRequest.FragmentLength; + RPCPDU rpcReply = RemoteServiceHelper.GetRPCReply(rpcRequest, m_service); + byte[] replyData = rpcReply.GetBytes(); + Append(replyData); + } + while (lengthOfPDUs < count); + } + + private void Append(byte[] buffer) + { + long position = m_outputStream.Position; + m_outputStream.Position = m_outputStream.Length; + m_outputStream.Write(buffer, 0, buffer.Length); + m_outputStream.Seek(position, SeekOrigin.Begin); + } + + public override void Flush() + { + } + + public override void Close() + { + m_outputStream.Close(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override bool CanSeek + { + get + { + return false; + } + } + + public override bool CanRead + { + get + { + return m_outputStream.CanRead; + } + } + + public override bool CanWrite + { + get + { + return m_outputStream.CanWrite; + } + } + + public override long Length + { + get + { + // Stream.Length only works on Stream implementations where seeking is available. + throw new NotSupportedException(); + } + } + + public override long Position + { + get + { + throw new NotSupportedException(); + } + set + { + throw new NotSupportedException(); + } + } + } +}