class RubySMB::SMB1::Tree
An SMB1
connected remote Tree
, as returned by a
RubySMB::SMB1::Packet::TreeConnectRequest
Attributes
The client this Tree
is connected through @!attribute [rw] client
@return [RubySMB::Client]
The current Guest Share Permissions @!attribute [rw] guest_permissions
@return [RubySMB::SMB1::BitField::DirectoryAccessMask]
The current Maximal Share Permissions @!attribute [rw] permissions
@return [RubySMB::SMB1::BitField::DirectoryAccessMask]
Public Class Methods
# File lib/ruby_smb/smb1/tree.rb, line 31 def initialize(client:, share:, response:) @client = client @share = share @id = response.smb_header.tid @guest_permissions = response.parameter_block.guest_access_rights @permissions = response.parameter_block.access_rights end
Public Instance Methods
Disconnects this Tree
from the current session
@return [WindowsError::ErrorCode] the NTStatus sent back by the server. @raise [RubySMB::Error::InvalidPacket] if the response is not a TreeDisconnectResponse packet
# File lib/ruby_smb/smb1/tree.rb, line 43 def disconnect! request = RubySMB::SMB1::Packet::TreeDisconnectRequest.new request = set_header_fields(request) raw_response = client.send_recv(request) response = RubySMB::SMB1::Packet::TreeDisconnectResponse.read(raw_response) unless response.valid? raise RubySMB::Error::InvalidPacket.new( expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID, expected_cmd: RubySMB::SMB1::Packet::TreeDisconnectResponse::COMMAND, packet: response ) end response.status_code end
List `directory` on the remote share.
@example
tree = client.tree_connect("\\\\192.168.99.134\\Share") tree.list(directory: "path\\to\\directory")
@param directory [String] path to the directory to be listed @param pattern [String] search pattern @param type [Class] file information class @return [Array] array of directory structures @raise [RubySMB::Error::InvalidPacket] if the response is not a Trans2 packet @raise [RubySMB::Error::UnexpectedStatusCode] if the response NTStatus is not STATUS_SUCCESS
# File lib/ruby_smb/smb1/tree.rb, line 174 def list(directory: '\\', pattern: '*', unicode: true, type: RubySMB::SMB1::Packet::Trans2::FindInformationLevel::FindFileFullDirectoryInfo) find_first_request = RubySMB::SMB1::Packet::Trans2::FindFirst2Request.new find_first_request = set_header_fields(find_first_request) find_first_request.smb_header.flags2.unicode = 1 if unicode search_path = directory.dup search_path << '\\' unless search_path.end_with?('\\') search_path << pattern search_path = '\\' + search_path unless search_path.start_with?('\\') # Set the search parameters t2_params = find_first_request.data_block.trans2_parameters t2_params.search_attributes.hidden = 1 t2_params.search_attributes.system = 1 t2_params.search_attributes.directory = 1 t2_params.flags.close_eos = 1 t2_params.flags.resume_keys = 0 t2_params.information_level = type::CLASS_LEVEL t2_params.filename = search_path t2_params.search_count = 10 find_first_request = set_find_params(find_first_request) raw_response = client.send_recv(find_first_request) response = RubySMB::SMB1::Packet::Trans2::FindFirst2Response.read(raw_response) unless response.valid? raise RubySMB::Error::InvalidPacket.new( expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID, expected_cmd: RubySMB::SMB1::Packet::Trans2::FindFirst2Response::COMMAND, packet: response ) end unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS raise RubySMB::Error::UnexpectedStatusCode, response.status_code end results = response.results(type, unicode: unicode) eos = response.data_block.trans2_parameters.eos sid = response.data_block.trans2_parameters.sid last = results.last.file_name while eos.zero? find_next_request = RubySMB::SMB1::Packet::Trans2::FindNext2Request.new find_next_request = set_header_fields(find_next_request) find_next_request.smb_header.flags2.unicode = 1 if unicode t2_params = find_next_request.data_block.trans2_parameters t2_params.sid = sid t2_params.flags.close_eos = 1 t2_params.flags.resume_keys = 0 t2_params.information_level = type::CLASS_LEVEL t2_params.filename = last t2_params.search_count = 10 find_next_request = set_find_params(find_next_request) raw_response = client.send_recv(find_next_request) response = RubySMB::SMB1::Packet::Trans2::FindNext2Response.read(raw_response) unless response.valid? raise RubySMB::Error::InvalidPacket.new( expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID, expected_cmd: RubySMB::SMB1::Packet::Trans2::FindNext2Response::COMMAND, packet: response ) end unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS raise RubySMB::Error::UnexpectedStatusCode, response.status_code end results += response.results(type, unicode: unicode) eos = response.data_block.trans2_parameters.eos last = results.last.file_name end results end
Open a file on the remote share.
@example
tree = client.tree_connect("\\\\192.168.99.134\\Share") tree.open_file(filename: "myfile")
@param filename [String] name of the file to be opened @param flags [BinData::Struct, Hash] flags to setup the request (see {RubySMB::SMB1::Packet::NtCreateAndxRequest}) @param options [RubySMB::SMB1::BitField::CreateOptions, Hash] flags that defines how the file should be created @param disposition [Integer] 32-bit field that defines how an already-existing file or a new file needs to be handled (constants are defined in {RubySMB::Dispositions}) @param impersonation [Integer] 32-bit field that defines the impersonation level (constants are defined in {RubySMB::ImpersonationLevels}) @param read [TrueClass, FalseClass] request a read access @param write [TrueClass, FalseClass] request a write access @param delete [TrueClass, FalseClass] request a delete access @return [RubySMB::SMB1::File] handle to the created file @raise [RubySMB::Error::InvalidPacket] if the response command is not SMB_COM_NT_CREATE_ANDX @raise [RubySMB::Error::UnexpectedStatusCode] if the response NTStatus is not STATUS_SUCCESS
# File lib/ruby_smb/smb1/tree.rb, line 83 def open_file(filename:, flags: nil, options: nil, disposition: RubySMB::Dispositions::FILE_OPEN, impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE, read: true, write: false, delete: false) nt_create_andx_request = RubySMB::SMB1::Packet::NtCreateAndxRequest.new nt_create_andx_request = set_header_fields(nt_create_andx_request) nt_create_andx_request.parameter_block.ext_file_attributes.normal = 1 if flags nt_create_andx_request.parameter_block.flags = flags else nt_create_andx_request.parameter_block.flags.request_extended_response = 1 end if options nt_create_andx_request.parameter_block.create_options = options else nt_create_andx_request.parameter_block.create_options.directory_file = 0 nt_create_andx_request.parameter_block.create_options.non_directory_file = 1 end if read nt_create_andx_request.parameter_block.share_access.share_read = 1 nt_create_andx_request.parameter_block.desired_access.read_data = 1 nt_create_andx_request.parameter_block.desired_access.read_ea = 1 nt_create_andx_request.parameter_block.desired_access.read_attr = 1 nt_create_andx_request.parameter_block.desired_access.read_control = 1 end if write nt_create_andx_request.parameter_block.share_access.share_write = 1 nt_create_andx_request.parameter_block.desired_access.write_data = 1 nt_create_andx_request.parameter_block.desired_access.append_data = 1 nt_create_andx_request.parameter_block.desired_access.write_ea = 1 nt_create_andx_request.parameter_block.desired_access.write_attr = 1 end if delete nt_create_andx_request.parameter_block.share_access.share_delete = 1 nt_create_andx_request.parameter_block.desired_access.delete_access = 1 end nt_create_andx_request.parameter_block.impersonation_level = impersonation nt_create_andx_request.parameter_block.create_disposition = disposition unicode_enabled = nt_create_andx_request.smb_header.flags2.unicode == 1 nt_create_andx_request.data_block.file_name = add_null_termination(str: filename, unicode: unicode_enabled) raw_response = @client.send_recv(nt_create_andx_request) response = RubySMB::SMB1::Packet::NtCreateAndxResponse.read(raw_response) unless response.valid? if response.is_a?(RubySMB::SMB1::Packet::EmptyPacket) && response.smb_header.protocol == RubySMB::SMB1::SMB_PROTOCOL_ID && response.smb_header.command == response.original_command raise RubySMB::Error::InvalidPacket.new( 'The response seems to be an SMB1 NtCreateAndxResponse but an '\ 'error occurs while parsing it. It is probably missing the '\ 'required extended information.' ) end raise RubySMB::Error::InvalidPacket.new( expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID, expected_cmd: RubySMB::SMB1::Packet::NtCreateAndxResponse::COMMAND, packet: response ) end unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS raise RubySMB::Error::UnexpectedStatusCode, response.status_code end case response.parameter_block.resource_type when RubySMB::SMB1::ResourceType::BYTE_MODE_PIPE, RubySMB::SMB1::ResourceType::MESSAGE_MODE_PIPE RubySMB::SMB1::Pipe.new(name: filename, tree: self, response: response) when RubySMB::SMB1::ResourceType::DISK RubySMB::SMB1::File.new(name: filename, tree: self, response: response) else raise RubySMB::Error::RubySMBError end end
# File lib/ruby_smb/smb1/tree.rb, line 58 def open_pipe(opts) # Make sure we don't modify the caller's hash options opts = opts.dup opts[:filename] = opts[:filename].dup opts[:filename].prepend('\\') unless opts[:filename].start_with?('\\') open_file(**opts) end
Sets a few preset header fields that will always be set the same way for Tree
operations. This is, the TreeID and Extended Attributes.
@param [RubySMB::SMB::Packet] the request packet to modify @return [RubySMB::SMB::Packet] the modified packet.
# File lib/ruby_smb/smb1/tree.rb, line 259 def set_header_fields(request) request.smb_header.tid = @id request.smb_header.flags2.eas = 1 request end
Private Instance Methods
Add null termination to `str` in case it is not already null-terminated.
@str [String] the string to be null-terminated @unicode [TrueClass, FalseClass] True if the null-termination should be Unicode encoded @return [String] the null-terminated string
# File lib/ruby_smb/smb1/tree.rb, line 285 def add_null_termination(str:, unicode: false) null_termination = unicode ? "\x00".encode('UTF-16LE') : "\x00" if str.end_with?(null_termination) return str else return str + null_termination end end
Sets ParameterBlock
options for FIND_FIRST2 and FIND_NEXT2 requests. In particular we need to do this to tell the server to ignore the Trans2DataBlock as we are not sending any GEA lists in this instance.
# File lib/ruby_smb/smb1/tree.rb, line 271 def set_find_params(request) request.parameter_block.data_count = 0 request.parameter_block.data_offset = 0 request.parameter_block.total_parameter_count = request.parameter_block.parameter_count request.parameter_block.max_parameter_count = request.parameter_block.parameter_count request.parameter_block.max_data_count = 16_384 request end