diff --git a/lib/ruby_smb/server/server_client.rb b/lib/ruby_smb/server/server_client.rb index e340f013f..c79d31789 100644 --- a/lib/ruby_smb/server/server_client.rb +++ b/lib/ruby_smb/server/server_client.rb @@ -36,6 +36,7 @@ def initialize(server, dispatcher) # session id => session instance @session_table = {} + @smb2_related_operations_state = {} end # @@ -334,9 +335,23 @@ def handle_smb1(raw_request, header) # @raise [NotImplementedError] Raised when the requested operation is not # supported. def handle_smb2(raw_request, header) - session = @session_table[header.session_id] + session_required = !(header.command == SMB2::Commands::SESSION_SETUP && header.session_id == 0) + + if header.flags.related_operations == 0 + @smb2_related_operations_state.clear + session = @session_table[header.session_id] + @smb2_related_operations_state[:session_id] = header.session_id + else + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/46dd4182-62d3-4e30-9fe5-e2ec124edca1 + if @smb2_related_operations_state.fetch(:session_id) == 0 && session_required + response = SMB2::Packet::ErrorPacket.new + response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER + return response + end + session = @session_table[@smb2_related_operations_state[:session_id]] + end - if session.nil? && !(header.command == SMB2::Commands::SESSION_SETUP && header.session_id == 0) + if session.nil? && session_required response = SMB2::Packet::ErrorPacket.new response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_USER_SESSION_DELETED return response @@ -387,7 +402,13 @@ def handle_smb2(raw_request, header) end logger.debug("Dispatching request to #{dispatcher} (session: #{session.inspect})") - send(dispatcher, request, session) + response = send(dispatcher, request, session) + + if response.is_a?(SMB2::Packet::ErrorPacket) + @smb2_related_operations_state.clear + end + + response end def _handle_smb2(raw_request) diff --git a/lib/ruby_smb/server/server_client/session_setup.rb b/lib/ruby_smb/server/server_client/session_setup.rb index 3be1285be..350144bb8 100644 --- a/lib/ruby_smb/server/server_client/session_setup.rb +++ b/lib/ruby_smb/server/server_client/session_setup.rb @@ -53,10 +53,12 @@ def do_logoff_andx_smb1(request, session) end def do_session_setup_smb2(request, session) + @smb2_related_operations_state.delete(:session_id) + session_id = request.smb2_header.session_id if session_id == 0 session_id = rand(1..0xfffffffe) - session = @session_table[session_id] = Session.new(session_id) + session = Session.new(session_id) else session = @session_table[session_id] if session.nil? @@ -92,6 +94,10 @@ def do_session_setup_smb2(request, session) update_preauth_hash(response) end + + @session_table[session_id] = session + @smb2_related_operations_state[:session_id] = session_id + response end diff --git a/lib/ruby_smb/server/server_client/share_io.rb b/lib/ruby_smb/server/server_client/share_io.rb index 840e32b28..9e2f5e09c 100644 --- a/lib/ruby_smb/server/server_client/share_io.rb +++ b/lib/ruby_smb/server/server_client/share_io.rb @@ -20,16 +20,46 @@ def proxy_share_io_smb1(request, session) alias :do_transactions2_smb1 :proxy_share_io_smb1 def proxy_share_io_smb2(request, session) - # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/9a639360-87be-4d49-a1dd-4c6be0c020bd - share_processor = session.tree_connect_table[request.smb2_header.tree_id] + if request.smb2_header.flags.related_operations == 0 + # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/9a639360-87be-4d49-a1dd-4c6be0c020bd + share_processor = session.tree_connect_table[request.smb2_header.tree_id] + @smb2_related_operations_state[:tree_id] = request.smb2_header.tree_id + else + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/46dd4182-62d3-4e30-9fe5-e2ec124edca1 + if @smb2_related_operations_state.fetch(:tree_id) == 0 + response = SMB2::Packet::ErrorPacket.new + response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER + return response + end + share_processor = session.tree_connect_table[@smb2_related_operations_state[:tree_id]] + end + if share_processor.nil? response = SMB2::Packet::ErrorPacket.new response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NETWORK_NAME_DELETED return response end + if request.field_names.include?(:file_id) + if request.smb2_header.flags.related_operations == 0 + @smb2_related_operations_state[:file_id] = request.file_id + elsif @smb2_related_operations_state[:file_id].nil? + response = SMB2::Packet::ErrorPacket.new + response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE + return response + else + request.file_id = @smb2_related_operations_state[:file_id] + end + end + logger.debug("Received #{SMB2::Commands.name(request.smb2_header.command)} request for share: #{share_processor.provider.name}") - share_processor.share_io(__callee__, request) + response = share_processor.share_io(__callee__, request) + + if response.field_names.include?(:file_id) + @smb2_related_operations_state[:file_id] = response.file_id + end + + response end alias :do_close_smb2 :proxy_share_io_smb2 diff --git a/lib/ruby_smb/server/server_client/tree_connect.rb b/lib/ruby_smb/server/server_client/tree_connect.rb index 3a73b9634..193cc9014 100644 --- a/lib/ruby_smb/server/server_client/tree_connect.rb +++ b/lib/ruby_smb/server/server_client/tree_connect.rb @@ -41,6 +41,8 @@ def do_tree_disconnect_smb1(request, session) end def do_tree_connect_smb2(request, session) + @smb2_related_operations_state.delete(:tree_id) + response = RubySMB::SMB2::Packet::TreeConnectResponse.new response.smb2_header.credits = 1 if session.tree_connect_table.length >= MAX_TREE_CONNECTIONS @@ -75,6 +77,8 @@ def do_tree_connect_smb2(request, session) session.tree_connect_table[tree_id] = share_processor = share_provider.new_processor(self, session) response.maximal_access = share_processor.maximal_access + @smb2_related_operations_state[:tree_id] = tree_id + response end