Class: RubySMB::SMB1::Tree

Inherits:
Object
  • Object
show all
Includes:
Rap::NetShareEnum
Defined in:
lib/ruby_smb/smb1/tree.rb

Overview

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

Constant Summary

Constants included from Rap::NetShareEnum

Rap::NetShareEnum::DATA_DESCRIPTOR_LEVEL_1, Rap::NetShareEnum::DEFAULT_RECEIVE_BUFFER_SIZE, Rap::NetShareEnum::OPCODE, Rap::NetShareEnum::PARAM_DESCRIPTOR, Rap::NetShareEnum::SHARE_TYPES, Rap::NetShareEnum::STYPE_SPECIAL, Rap::NetShareEnum::STYPE_TEMPORARY

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Rap::NetShareEnum

#net_share_enum

Constructor Details

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

Returns a new instance of Tree.



36
37
38
39
40
41
42
# File 'lib/ruby_smb/smb1/tree.rb', line 36

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

Instance Attribute Details

#clientRubySMB::Client

Returns:



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

def client
  @client
end

#guest_permissionsRubySMB::SMB1::BitField::DirectoryAccessMask



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

def guest_permissions
  @guest_permissions
end

#idInteger

Returns:

  • (Integer)


34
35
36
# File 'lib/ruby_smb/smb1/tree.rb', line 34

def id
  @id
end

#permissionsRubySMB::SMB1::BitField::DirectoryAccessMask



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

def permissions
  @permissions
end

#shareString

Returns:

  • (String)


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

def share
  @share
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:



48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/ruby_smb/smb1/tree.rb', line 48

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

#enable_cifs_unix_extensionsWindowsError::ErrorCode

Perform the CIFS UNIX Extensions handshake on this tree: query the server's supported extensions (SMB_QUERY_CIFS_UNIX_INFO) and then echo the version and capability bits back via SMB_SET_CIFS_UNIX_INFO. Samba requires this round-trip to have completed on the current session before it will honor UNIX-extension info levels such as SMB_SET_FILE_UNIX_LINK.

Returns:

  • (WindowsError::ErrorCode)

    NTStatus of the SET reply

Raises:



278
279
280
281
# File 'lib/ruby_smb/smb1/tree.rb', line 278

def enable_cifs_unix_extensions
  major, minor, capabilities = query_cifs_unix_info
  set_cifs_unix_info(major: major, minor: minor, capabilities: capabilities)
end

#list(directory: '\\', pattern: '*', unicode: true, type: RubySMB::SMB1::Packet::Trans2::FindInformationLevel::FindFileFullDirectoryInfo) ⇒ 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: '\\')

    path to the directory to be listed

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

    search pattern

  • type (Class) (defaults to: RubySMB::SMB1::Packet::Trans2::FindInformationLevel::FindFileFullDirectoryInfo)

    file information class

Returns:

  • (Array)

    array of directory structures

Raises:



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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/ruby_smb/smb1/tree.rb', line 108

def list(directory: '\\', pattern: '*', unicode: true,
         type: RubySMB::SMB1::Packet::Trans2::FindInformationLevel::FindFileFullDirectoryInfo)
  info_standard = (type == RubySMB::SMB1::Packet::Trans2::FindInformationLevel::FindInfoStandard)

  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 && !info_standard

  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                = info_standard ? 255 : 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

  t2p_override, t2d_override = response.win9x_trans2_overrides(raw_response)
  results = if t2d_override
              response.results(type, unicode: unicode, buffer: t2d_override)
            else
              response.results(type, unicode: unicode)
            end

  effective_params = t2p_override || response.data_block.trans2_parameters
  eos   = effective_params.eos
  sid   = effective_params.sid
  last  = results.last&.file_name

  while eos.zero? && last
    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

    batch = response.results(type, unicode: unicode)
    break if batch.empty?

    results += batch
    eos   = response.data_block.trans2_parameters.eos
    last  = results.last.file_name
  end

  results
end

#open_file(opts) ⇒ RubySMB::SMB1::File

Open a file on the remote share.

Examples:

tree = client.tree_connect("\\\\192.168.99.134\\Share")
tree.open_file(filename: "myfile")

Parameters:

  • filename (String)

    name of the file to be opened

  • flags (BinData::Struct, Hash)

    flags to setup the request (see Packet::NtCreateAndxRequest)

  • options (RubySMB::SMB1::BitField::CreateOptions, Hash)

    flags that defines how the file should be created

  • disposition (Integer)

    32-bit field that defines how an already-existing file or a new file needs to be handled (constants are defined in Dispositions)

  • impersonation (Integer)

    32-bit field that defines the impersonation level (constants are defined in ImpersonationLevels)

  • read (TrueClass, FalseClass)

    request a read access

  • write (TrueClass, FalseClass)

    request a write access

  • delete (TrueClass, FalseClass)

    request a delete access

Returns:

Raises:



88
89
90
91
92
93
94
# File 'lib/ruby_smb/smb1/tree.rb', line 88

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



63
64
65
66
67
68
69
# File 'lib/ruby_smb/smb1/tree.rb', line 63

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

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

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

Parameters:

  • the (RubySMB::SMB::Packet)

    request packet to modify

Returns:

  • (RubySMB::SMB::Packet)

    the modified packet.



263
264
265
266
267
# File 'lib/ruby_smb/smb1/tree.rb', line 263

def set_header_fields(request)
  request.smb_header.tid        = @id
  request.smb_header.flags2.eas = 1
  request
end

Create a UNIX symbolic link on the remote share using the CIFS UNIX Extensions SMB_SET_FILE_UNIX_LINK information level (0x0201) via a Trans2 SET_PATH_INFORMATION request.

Examples:

tree = client.tree_connect("\\\\samba\\writable")
tree.set_unix_link(symlink: 'escape', target: '../../../../etc')

Parameters:

  • symlink (String)

    the path, relative to the share, where the symbolic link file will be created

  • target (String)

    the destination the symbolic link points to

Returns:

  • (WindowsError::ErrorCode)

    the NTStatus returned by the server

Raises:



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/ruby_smb/smb1/tree.rb', line 214

def set_unix_link(symlink:, target:)
  # Samba gates SMB_SET_FILE_UNIX_LINK behind a per-session CIFS UNIX
  # Extensions handshake: query server capabilities, then echo them
  # back via SMB_SET_CIFS_UNIX_INFO. Without this, Samba responds to
  # the symlink request with STATUS_INVALID_LEVEL even when
  # `unix extensions = yes` is set server-side.
  enable_cifs_unix_extensions

  request = RubySMB::SMB1::Packet::Trans2::SetPathInformationRequest.new
  request = set_header_fields(request)
  # CIFS UNIX Extensions paths are raw byte strings, not UTF-16. Clear
  # flags2.unicode so the filename parameter and target data block are
  # emitted as bytes — Samba rejects the UTF-16 variant with
  # STATUS_INVALID_LEVEL.
  request.smb_header.flags2.unicode = 0

  t2_params = request.data_block.trans2_parameters
  t2_params.information_level = RubySMB::SMB1::Packet::Trans2::SetInformationLevel::SMB_SET_FILE_UNIX_LINK
  t2_params.filename = symlink

  encoded_target = target.dup.force_encoding(Encoding::ASCII_8BIT)
  encoded_target << "\x00".b unless encoded_target.end_with?("\x00".b)
  request.data_block.trans2_data.buffer = encoded_target

  request.parameter_block.total_parameter_count = request.parameter_block.parameter_count
  request.parameter_block.total_data_count      = request.parameter_block.data_count
  request.parameter_block.max_parameter_count   = request.parameter_block.parameter_count
  request.parameter_block.max_data_count        = 0

  raw_response = client.send_recv(request)
  response = RubySMB::SMB1::Packet::Trans2::SetPathInformationResponse.read(raw_response)
  unless response.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB1::Packet::Trans2::SetPathInformationResponse::COMMAND,
      packet:         response
    )
  end
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
    raise RubySMB::Error::UnexpectedStatusCode, response.status_code
  end
  response.status_code
end