Class: RubySMB::SMB2::Tree

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_smb/smb2/tree.rb

Overview

An SMB2 connected remote Tree, as returned by a [RubySMB::SMB2::Packet::TreeConnectRequest]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client:, share:, response:, encrypt: false) ⇒ Tree

Returns a new instance of Tree.



31
32
33
34
35
36
37
38
# 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

Instance Attribute Details

#clientRubySMB::Client

Returns:



9
10
11
# File 'lib/ruby_smb/smb2/tree.rb', line 9

def client
  @client
end

#idInteger

Returns:

  • (Integer)


24
25
26
# File 'lib/ruby_smb/smb2/tree.rb', line 24

def id
  @id
end

#permissionsRubySMB::SMB2::BitField::DirectoryAccessMask



14
15
16
# File 'lib/ruby_smb/smb2/tree.rb', line 14

def permissions
  @permissions
end

#shareString

Returns:

  • (String)


19
20
21
# File 'lib/ruby_smb/smb2/tree.rb', line 19

def share
  @share
end

#tree_connect_encrypt_dataBoolean

Returns:

  • (Boolean)


29
30
31
# File 'lib/ruby_smb/smb2/tree.rb', line 29

def tree_connect_encrypt_data
  @tree_connect_encrypt_data
end

Instance Method Details

#disconnect!WindowsError::ErrorCode

Disconnects this Tree from the current session

Returns:

  • (WindowsError::ErrorCode)

    the NTStatus sent back by the server.

Raises:



44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 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) ⇒ Array

List directory on the remote share.

Examples:

tree = client.tree_connect("\\\\192.168.99.134\\Share")
tree.list(directory: "path\\to\\directory")

Parameters:

  • directory (String) (defaults to: nil)

    path to the directory to be listed

  • pattern (String) (defaults to: '*')

    search pattern

  • type (Class) (defaults to: RubySMB::Fscc::FileInformation::FileIdFullDirectoryInformation)

    file information class

Returns:

  • (Array)

    array of directory structures

Raises:



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/ruby_smb/smb2/tree.rb', line 86

def list(directory: nil, pattern: '*', type: RubySMB::Fscc::FileInformation::FileIdFullDirectoryInformation)
  create_response = open_directory(directory: directory)
  opened_directory = RubySMB::SMB2::File.new(tree: self, response: create_response, name: 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
ensure
  opened_directory.close if opened_directory
end

#open_directory(directory: nil, disposition: RubySMB::Dispositions::FILE_OPEN, impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE, read: true, write: false, delete: false, desired_delete: false) ⇒ RubySMB::SMB2::Packet::CreateResponse

'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.

Parameters:

  • directory (String) (defaults to: nil)

    the name of the directory file

  • disposition (Integer) (defaults to: RubySMB::Dispositions::FILE_OPEN)

    the create disposition to use, should be one of Dispositions

  • impersonation (Integer) (defaults to: RubySMB::ImpersonationLevels::SEC_IMPERSONATE)

    the impersonation level to use, should be one of ImpersonationLevels

  • read (Boolean) (defaults to: true)

    whether to request read access

  • write (Boolean) (defaults to: false)

    whether to request write access

  • delete (Boolean) (defaults to: false)

    whether to request delete access

Returns:

Raises:



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/ruby_smb/smb2/tree.rb', line 148

def open_directory(directory: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
                   impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE,
                   read: true, write: false, delete: false, desired_delete: false)

  create_request  = open_directory_packet(directory: directory, disposition: disposition,
                                          impersonation: impersonation, read: read, write: write, delete: delete,
                                          desired_delete: desired_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, desired_delete: false) ⇒ RubySMB::SMB2::Packet::CreateRequest

Creates the Packet for the #open_directory method.

Parameters:

  • directory (String) (defaults to: nil)

    the name of the directory file

  • disposition (Integer) (defaults to: RubySMB::Dispositions::FILE_OPEN)

    the create disposition to use, should be one of Dispositions

  • impersonation (Integer) (defaults to: RubySMB::ImpersonationLevels::SEC_IMPERSONATE)

    the impersonation level to use, should be one of ImpersonationLevels

  • read (Boolean) (defaults to: true)

    whether to request read access

  • write (Boolean) (defaults to: false)

    whether to request write access

  • delete (Boolean) (defaults to: false)

    whether to request delete access

Returns:



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/ruby_smb/smb2/tree.rb', line 180

def open_directory_packet(directory: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
                          impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE,
                          read: true, write: false, delete: false, desired_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.desired_access.delete_access   = 1 if desired_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(opts) ⇒ Object



67
68
69
70
71
72
73
# File 'lib/ruby_smb/smb2/tree.rb', line 67

def open_file(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?('\\'.encode(opts[:filename].encoding))
  _open(**opts)
end

#open_pipe(opts) ⇒ Object



59
60
61
62
63
64
65
# 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?('\\'.encode(opts[:filename].encoding))
  _open(**opts)
end

#set_header_fields(request) ⇒ RubySMB::SMB2::Packet

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.

Parameters:

Returns:



210
211
212
213
214
# File 'lib/ruby_smb/smb2/tree.rb', line 210

def set_header_fields(request)
  request.smb2_header.tree_id = id
  request.smb2_header.credits = 256
  request
end