class RubySMB::SMB2::Tree

An SMB2 connected remote Tree, as returned by a

RubySMB::SMB2::Packet::TreeConnectRequest

Attributes

client[RW]

The client this Tree is connected through @!attribute [rw] client

@return [RubySMB::Client]
id[RW]

The Tree ID for this Tree @!attribute [rw] id

@return [Integer]
permissions[RW]

The current Maximal Share Permissions @!attribute [rw] permissions

@return [RubySMB::SMB2::BitField::DirectoryAccessMask]
share[RW]

The share path associated with this Tree @!attribute [rw] share

@return [String]
tree_connect_encrypt_data[RW]

Whether or not the share associated with this tree connect needs to be encrypted (SMB 3.x) @!attribute [rw] tree_connect_encrypt_data

@return [Boolean]

Public Class Methods

new(client:, share:, response:, encrypt: false) click to toggle source
# File lib/ruby_smb/smb2/tree.rb, line 31
def initialize(client:, share:, response:, encrypt: false)
  @client              = client
  @share               = share
  @id                  = response.smb2_header.tree_id
  @permissions         = response.maximal_access
  @share_type          = response.share_type
  @tree_connect_encrypt_data = encrypt
end

Public Instance Methods

disconnect!() click to toggle source

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/smb2/tree.rb, line 44
def disconnect!
  request = RubySMB::SMB2::Packet::TreeDisconnectRequest.new
  request = set_header_fields(request)
  raw_response = client.send_recv(request, encrypt: @tree_connect_encrypt_data)
  response = RubySMB::SMB2::Packet::TreeDisconnectResponse.read(raw_response)
  unless response.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB2::Packet::TreeDisconnectResponse::COMMAND,
      packet:         response
    )
  end
  response.status_code
end
list(directory: nil, pattern: '*', type: RubySMB::Fscc::FileInformation::FileIdFullDirectoryInformation) click to toggle source

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 QueryDirectoryResponse packet

# File lib/ruby_smb/smb2/tree.rb, line 148
def list(directory: nil, pattern: '*', type: RubySMB::Fscc::FileInformation::FileIdFullDirectoryInformation)
  create_response = open_directory(directory: directory)
  file_id         = create_response.file_id

  directory_request                         = RubySMB::SMB2::Packet::QueryDirectoryRequest.new
  directory_request.file_information_class  = type::CLASS_LEVEL
  directory_request.file_id                 = file_id
  directory_request.name                    = pattern

  max_read = client.server_max_read_size
  max_read = 65536 unless client.server_supports_multi_credit
  credit_charge = 0
  if client.server_supports_multi_credit
    credit_charge = (max_read - 1) / 65536 + 1
  end
  directory_request.output_length = max_read
  directory_request.smb2_header.credit_charge = credit_charge

  directory_request = set_header_fields(directory_request)

  files = []
  loop do
    response            = client.send_recv(directory_request, encrypt: @tree_connect_encrypt_data)
    directory_response  = RubySMB::SMB2::Packet::QueryDirectoryResponse.read(response)
    unless directory_response.valid?
      raise RubySMB::Error::InvalidPacket.new(
        expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
        expected_cmd:   RubySMB::SMB2::Packet::QueryDirectoryResponse::COMMAND,
        packet:         directory_response
      )
    end

    status_code         = directory_response.smb2_header.nt_status.to_nt_status

    break if status_code == WindowsError::NTStatus::STATUS_NO_MORE_FILES

    unless status_code == WindowsError::NTStatus::STATUS_SUCCESS
      raise RubySMB::Error::UnexpectedStatusCode, status_code
    end

    files += directory_response.results(type)
    # Reset the message id so the client can update appropriately.
    directory_request.smb2_header.message_id = 0
  end

  files
end
open_directory(directory: nil, disposition: RubySMB::Dispositions::FILE_OPEN, impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE, read: true, write: false, delete: false) click to toggle source

'Opens' a directory file on the remote end, using a CreateRequest. This can be used to open an existing directory, or create a new one, depending on the disposition set.

@param directory [String] the name of the directory file @param disposition [Integer] the create disposition to use, should be one of {RubySMB::Dispositions} @param impersonation [Integer] the impersonation level to use, should be one of {RubySMB::ImpersonationLevels} @param read [Boolean] whether to request read access @param write [Boolean] whether to request write access @param delete [Boolean] whether to request delete access @return [RubySMB::SMB2::Packet::CreateResponse] the response packet returned from the server @raise [RubySMB::Error::InvalidPacket] if the response is not a CreateResponse packet

# File lib/ruby_smb/smb2/tree.rb, line 208
def open_directory(directory: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
                   impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE,
                   read: true, write: false, delete: false)

  create_request  = open_directory_packet(directory: directory, disposition: disposition,
                                          impersonation: impersonation, read: read, write: write, delete: delete)
  raw_response    = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
  response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
  unless response.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB2::Packet::CreateResponse::COMMAND,
      packet:         response
    )
  end
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
    raise RubySMB::Error::UnexpectedStatusCode, response.status_code
  end

  response
end
open_directory_packet(directory: nil, disposition: RubySMB::Dispositions::FILE_OPEN, impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE, read: true, write: false, delete: false) click to toggle source

Creates the Packet for the open_directory method.

@param directory [String] the name of the directory file @param disposition [Integer] the create disposition to use, should be one of {RubySMB::Dispositions} @param impersonation [Integer] the impersonation level to use, should be one of {RubySMB::ImpersonationLevels} @param read [Boolean] whether to request read access @param write [Boolean] whether to request write access @param delete [Boolean] whether to request delete access @return [RubySMB::SMB2::Packet::CreateRequest] the request packet to send to the server

# File lib/ruby_smb/smb2/tree.rb, line 239
def open_directory_packet(directory: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
                          impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE,
                          read: true, write: false, delete: false)
  create_request = RubySMB::SMB2::Packet::CreateRequest.new
  create_request = set_header_fields(create_request)

  create_request.impersonation_level            = impersonation
  create_request.create_options.directory_file  = 1
  create_request.file_attributes.directory      = 1
  create_request.desired_access.list            = 1
  create_request.share_access.read_access       = 1 if read
  create_request.share_access.write_access      = 1 if write
  create_request.share_access.delete_access     = 1 if delete
  create_request.create_disposition             = disposition

  if directory.nil? || directory.empty?
    create_request.name = "\x00"
    create_request.name_length = 0
  else
    create_request.name = directory
  end
  create_request
end
open_file(filename:, attributes: nil, options: nil, disposition: RubySMB::Dispositions::FILE_OPEN, impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE, read: true, write: false, delete: false) click to toggle source
# File lib/ruby_smb/smb2/tree.rb, line 67
def open_file(filename:, attributes: nil, options: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
              impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE, read: true, write: false, delete: false)

  create_request = RubySMB::SMB2::Packet::CreateRequest.new
  create_request = set_header_fields(create_request)

  # If the user supplied file attributes, use those, otherwise set some
  # sane defaults.
  if attributes
    create_request.file_attributes = attributes
  else
    create_request.file_attributes.directory  = 0
    create_request.file_attributes.normal     = 1
  end

  # If the user supplied Create Options, use those, otherwise set some
  # sane defaults.
  if options
    create_request.create_options = options
  else
    create_request.create_options.directory_file      = 0
    create_request.create_options.non_directory_file  = 1
  end

  if read
    create_request.share_access.read_access = 1
    create_request.desired_access.read_data = 1
  end

  if write
    create_request.share_access.write_access   = 1
    create_request.desired_access.write_data   = 1
    create_request.desired_access.append_data  = 1
  end

  if delete
    create_request.share_access.delete_access   = 1
    create_request.desired_access.delete_access = 1
  end

  create_request.requested_oplock     = 0xff
  create_request.impersonation_level  = impersonation
  create_request.create_disposition   = disposition
  create_request.name                 = filename

  raw_response  = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
  response      = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
  unless response.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB2::Packet::CreateResponse::COMMAND,
      packet:         response
    )
  end
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
    raise RubySMB::Error::UnexpectedStatusCode, response.status_code
  end

  case @share_type
  when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_DISK
    RubySMB::SMB2::File.new(name: filename, tree: self, response: response, encrypt: @tree_connect_encrypt_data)
  when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PIPE
    RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
  # when RubySMB::SMB2::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
  #   it's a printer!
  else
    raise RubySMB::Error::RubySMBError, 'Unsupported share type'
  end
end
open_pipe(opts) click to toggle source
# File lib/ruby_smb/smb2/tree.rb, line 59
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] = opts[:filename][1..-1] if opts[:filename].start_with? '\\'
  open_file(**opts)
end
set_header_fields(request) click to toggle source

Sets a few preset header fields that will always be set the same way for Tree operations. This is, the TreeID, Credits, and Credit Charge.

@param [RubySMB::SMB2::Packet] the request packet to modify @return [RubySMB::SMB2::Packet] the modified packet.

# File lib/ruby_smb/smb2/tree.rb, line 268
def set_header_fields(request)
  request.smb2_header.tree_id = id
  request.smb2_header.credits = 256
  request
end