Class: RubySMB::SMB2::File

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

Overview

Represents a file on the Remote server that we can perform various I/O operations on.

Direct Known Subclasses

Pipe

Constant Summary collapse

MAX_PACKET_SIZE =

The maximum number of byte we want to read or write in a single packet.

32_768

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tree:, response:, name:, encrypt: false) ⇒ File

Returns a new instance of File.

Raises:

  • (ArgumentError)


60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/ruby_smb/smb2/file.rb', line 60

def initialize(tree:, response:, name:, encrypt: false)
  raise ArgumentError, 'No Tree Provided' if tree.nil?
  raise ArgumentError, 'No Response Provided' if response.nil?

  @tree = tree
  @name = name

  @attributes   = response.file_attributes
  @guid         = response.file_id
  @last_access  = response.last_access.to_datetime
  @last_change  = response.last_change.to_datetime
  @last_write   = response.last_write.to_datetime
  @size         = response.end_of_file
  @size_on_disk = response.allocation_size
  @tree_connect_encrypt_data = encrypt
end

Instance Attribute Details

#attributesRubySMB::Fscc::FileAttributes



13
14
15
# File 'lib/ruby_smb/smb2/file.rb', line 13

def attributes
  @attributes
end

#guidRubySMB::Field::Smb2FileId



18
19
20
# File 'lib/ruby_smb/smb2/file.rb', line 18

def guid
  @guid
end

#last_accessDateTime



23
24
25
# File 'lib/ruby_smb/smb2/file.rb', line 23

def last_access
  @last_access
end

#last_changeDateTime



28
29
30
# File 'lib/ruby_smb/smb2/file.rb', line 28

def last_change
  @last_change
end

#last_writeDateTime



33
34
35
# File 'lib/ruby_smb/smb2/file.rb', line 33

def last_write
  @last_write
end

#nameString



38
39
40
# File 'lib/ruby_smb/smb2/file.rb', line 38

def name
  @name
end

#sizeInteger



43
44
45
# File 'lib/ruby_smb/smb2/file.rb', line 43

def size
  @size
end

#size_on_diskInteger



48
49
50
# File 'lib/ruby_smb/smb2/file.rb', line 48

def size_on_disk
  @size_on_disk
end

#treeRubySMB::SMB2::Tree



53
54
55
# File 'lib/ruby_smb/smb2/file.rb', line 53

def tree
  @tree
end

#tree_connect_encrypt_dataBoolean



58
59
60
# File 'lib/ruby_smb/smb2/file.rb', line 58

def tree_connect_encrypt_data
  @tree_connect_encrypt_data
end

Instance Method Details

#append(data: '') ⇒ WindowsError::ErrorCode

Appends the supplied data to the end of the file.



81
82
83
# File 'lib/ruby_smb/smb2/file.rb', line 81

def append(data:'')
  write(data: data, offset: size)
end

#closeWindowsError::ErrorCode

Closes the handle to the remote file.

Raises:



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/ruby_smb/smb2/file.rb', line 90

def close
  close_request = set_header_fields(RubySMB::SMB2::Packet::CloseRequest.new)
  raw_response  = tree.client.send_recv(close_request, encrypt: @tree_connect_encrypt_data)
  response = RubySMB::SMB2::Packet::CloseResponse.read(raw_response)
  unless response.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB2::Packet::CloseResponse::COMMAND,
      received_proto: response.smb2_header.protocol,
      received_cmd:   response.smb2_header.command
    )
  end
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
    raise RubySMB::Error::UnexpectedStatusCode, response.status_code
  end
  response.status_code
end

#deleteWindowsError::ErrorCode

Delete a file on close

Raises:



206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/ruby_smb/smb2/file.rb', line 206

def delete
  raw_response = tree.client.send_recv(delete_packet, encrypt: @tree_connect_encrypt_data)
  response = RubySMB::SMB2::Packet::SetInfoResponse.read(raw_response)
  unless response.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB2::Packet::SetInfoResponse::COMMAND,
      received_proto: response.smb2_header.protocol,
      received_cmd:   response.smb2_header.command
    )
  end
  response.smb2_header.nt_status.to_nt_status
end

#delete_packetRubySMB::SMB2::Packet::SetInfoRequest

Crafts the SetInfoRequest packet to be sent for delete operations.



223
224
225
226
227
228
# File 'lib/ruby_smb/smb2/file.rb', line 223

def delete_packet
  delete_request                       = set_header_fields(RubySMB::SMB2::Packet::SetInfoRequest.new)
  delete_request.file_info_class       = RubySMB::Fscc::FileInformation::FILE_DISPOSITION_INFORMATION
  delete_request.buffer.delete_pending = 1
  delete_request
end

#read(bytes: size, offset: 0) ⇒ String

Read from the file, a specific number of bytes from a specific offset. If no parameters are given it will read the entire file.

Raises:



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
# File 'lib/ruby_smb/smb2/file.rb', line 117

def read(bytes: size, offset: 0)
  max_read = tree.client.server_max_read_size
  max_read = 65536 unless tree.client.server_supports_multi_credit
  atomic_read_size = [bytes, max_read].min
  credit_charge = 0
  if tree.client.server_supports_multi_credit
    credit_charge = (atomic_read_size - 1) / 65536 + 1
  end

  read_request = read_packet(read_length: atomic_read_size, offset: offset, credit_charge: credit_charge)
  raw_response = tree.client.send_recv(read_request, encrypt: @tree_connect_encrypt_data)
  response     = RubySMB::SMB2::Packet::ReadResponse.read(raw_response)
  unless response.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB2::Packet::ReadResponse::COMMAND,
      received_proto: response.smb2_header.protocol,
      received_cmd:   response.smb2_header.command
    )
  end
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
    raise RubySMB::Error::UnexpectedStatusCode, response.status_code
  end

  data = response.buffer.to_binary_s

  remaining_bytes = bytes - atomic_read_size

  while remaining_bytes > 0
    offset += atomic_read_size
    atomic_read_size = remaining_bytes if remaining_bytes < max_read

    read_request = read_packet(read_length: atomic_read_size, offset: offset, credit_charge: credit_charge)
    raw_response = tree.client.send_recv(read_request, encrypt: @tree_connect_encrypt_data)
    response     = RubySMB::SMB2::Packet::ReadResponse.read(raw_response)
    unless response.valid?
      raise RubySMB::Error::InvalidPacket.new(
        expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
        expected_cmd:   RubySMB::SMB2::Packet::ReadResponse::COMMAND,
        received_proto: response.smb2_header.protocol,
        received_cmd:   response.smb2_header.command
      )
    end
    unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
      raise RubySMB::Error::UnexpectedStatusCode, response.status_code
    end

    data << response.buffer.to_binary_s
    remaining_bytes -= atomic_read_size
  end
  data
end

#read_packet(read_length: 0, offset: 0, credit_charge: 1) ⇒ RubySMB::SMB2::Packet::ReadRequest

Crafts the ReadRequest packet to be sent for read operations.



176
177
178
179
180
181
182
# File 'lib/ruby_smb/smb2/file.rb', line 176

def read_packet(read_length: 0, offset: 0, credit_charge: 1)
  read_request = set_header_fields(RubySMB::SMB2::Packet::ReadRequest.new)
  read_request.read_length  = read_length
  read_request.offset       = offset
  read_request.smb2_header.credit_charge = credit_charge
  read_request
end

#rename(new_file_name) ⇒ WindowsError::ErrorCode

Rename a file

Raises:



315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/ruby_smb/smb2/file.rb', line 315

def rename(new_file_name)
  raw_response = tree.client.send_recv(rename_packet(new_file_name), encrypt: @tree_connect_encrypt_data)
  response = RubySMB::SMB2::Packet::SetInfoResponse.read(raw_response)
  unless response.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB2::Packet::SetInfoResponse::COMMAND,
      received_proto: response.smb2_header.protocol,
      received_cmd:   response.smb2_header.command
    )
  end
  response.smb2_header.nt_status.to_nt_status
end

#rename_packet(new_file_name) ⇒ RubySMB::SMB2::Packet::SetInfoRequest

Crafts the SetInfoRequest packet to be sent for rename operations.



333
334
335
336
337
338
# File 'lib/ruby_smb/smb2/file.rb', line 333

def rename_packet(new_file_name)
  rename_request                  = set_header_fields(RubySMB::SMB2::Packet::SetInfoRequest.new)
  rename_request.file_info_class  = RubySMB::Fscc::FileInformation::FILE_RENAME_INFORMATION
  rename_request.buffer.file_name = new_file_name.encode('utf-16le')
  rename_request
end

#send_recv_read(read_length: 0, offset: 0) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/ruby_smb/smb2/file.rb', line 184

def send_recv_read(read_length: 0, offset: 0)
  read_request = read_packet(read_length: read_length, offset: offset)
  raw_response = tree.client.send_recv(read_request, encrypt: @tree_connect_encrypt_data)
  response = RubySMB::SMB2::Packet::ReadResponse.read(raw_response)
  unless response.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB2::Packet::ReadResponse::COMMAND,
      received_proto: response.smb2_header.protocol,
      received_cmd:   response.smb2_header.command
    )
  end
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
    raise RubySMB::Error::UnexpectedStatusCode, response.status_code
  end
  response.buffer.to_binary_s
end

#send_recv_write(data: '', offset: 0) ⇒ Object



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/ruby_smb/smb2/file.rb', line 292

def send_recv_write(data:'', offset: 0)
  pkt = write_packet(data: data, offset: offset)
  raw_response = tree.client.send_recv(pkt, encrypt: @tree_connect_encrypt_data)
  response = RubySMB::SMB2::Packet::WriteResponse.read(raw_response)
  unless response.valid?
    raise RubySMB::Error::InvalidPacket.new(
      expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
      expected_cmd:   RubySMB::SMB2::Packet::WriteResponse::COMMAND,
      received_proto: response.smb2_header.protocol,
      received_cmd:   response.smb2_header.command
    )
  end
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
    raise RubySMB::Error::UnexpectedStatusCode, response.status_code
  end
  response.write_count
end

#set_header_fields(request) ⇒ RubySMB::GenericPacket

Sets the header fields that we have to set on every packet we send for File operations.



234
235
236
237
238
# File 'lib/ruby_smb/smb2/file.rb', line 234

def set_header_fields(request)
  request         = tree.set_header_fields(request)
  request.file_id = guid
  request
end

#write(data: '', offset: 0) ⇒ WindowsError::ErrorCode

Write the supplied data to the file at the given offset.

Raises:



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/ruby_smb/smb2/file.rb', line 246

def write(data:'', offset: 0)
  max_write = tree.client.server_max_write_size
  max_write = 65536 unless tree.client.server_supports_multi_credit
  buffer            = data.dup
  bytes             = data.length
  atomic_write_size = [bytes, max_write].min
  credit_charge = 0
  if tree.client.server_supports_multi_credit
    credit_charge = (atomic_write_size - 1) / 65536 + 1
  end

  while buffer.length > 0 do
    write_request = write_packet(data: buffer.slice!(0, atomic_write_size), offset: offset, credit_charge: credit_charge)
    raw_response  = tree.client.send_recv(write_request, encrypt: @tree_connect_encrypt_data)
    response      = RubySMB::SMB2::Packet::WriteResponse.read(raw_response)
    unless response.valid?
      raise RubySMB::Error::InvalidPacket.new(
        expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
        expected_cmd:   RubySMB::SMB2::Packet::WriteResponse::COMMAND,
        received_proto: response.smb2_header.protocol,
        received_cmd:   response.smb2_header.command
      )
    end
    status        = response.smb2_header.nt_status.to_nt_status

    offset += atomic_write_size
    return status unless status == WindowsError::NTStatus::STATUS_SUCCESS
  end

  status
end

#write_packet(data: '', offset: 0, credit_charge: 1) ⇒ Object

Creates the Request packet for the #write command



284
285
286
287
288
289
290
# File 'lib/ruby_smb/smb2/file.rb', line 284

def write_packet(data:'', offset: 0, credit_charge: 1)
  write_request               = set_header_fields(RubySMB::SMB2::Packet::WriteRequest.new)
  write_request.write_offset  = offset
  write_request.buffer        = data
  write_request.smb2_header.credit_charge = credit_charge
  write_request
end