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:) ⇒ File

Returns a new instance of File.

Raises:

  • (ArgumentError)


55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/ruby_smb/smb2/file.rb', line 55

def initialize(tree:, response:, name:)
  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
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

Returns:

  • (RubySMB::Field::Smb2FileId)


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

def guid
  @guid
end

#last_accessDateTime

Returns:

  • (DateTime)


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

def last_access
  @last_access
end

#last_changeDateTime

Returns:

  • (DateTime)


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

def last_change
  @last_change
end

#last_writeDateTime

Returns:

  • (DateTime)


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

def last_write
  @last_write
end

#nameString

Returns:

  • (String)


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

def name
  @name
end

#sizeInteger

Returns:

  • (Integer)


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

def size
  @size
end

#size_on_diskInteger

Returns:

  • (Integer)


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

def size_on_disk
  @size_on_disk
end

#treeRubySMB::SMB2::Tree

Returns:



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

def tree
  @tree
end

Instance Method Details

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

Appends the supplied data to the end of the file.

Parameters:

  • data (String) (defaults to: '')

    the data to write to the file

Returns:

  • (WindowsError::ErrorCode)

    the NTStatus code returned from the operation



75
76
77
# File 'lib/ruby_smb/smb2/file.rb', line 75

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

#closeWindowsError::ErrorCode

Closes the handle to the remote file.

Returns:

  • (WindowsError::ErrorCode)

    the NTStatus code returned by the operation

Raises:



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/ruby_smb/smb2/file.rb', line 84

def close
  close_request = set_header_fields(RubySMB::SMB2::Packet::CloseRequest.new)
  raw_response  = tree.client.send_recv(close_request)
  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.name
  end
  response.status_code
end

#deleteWindowsError::ErrorCode

Delete a file on close

Returns:

  • (WindowsError::ErrorCode)

    the NTStatus Response code

Raises:



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

def delete
  raw_response = tree.client.send_recv(delete_packet)
  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.

Returns:



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

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.

Parameters:

  • bytes (Integer) (defaults to: size)

    the number of bytes to read

  • offset (Integer) (defaults to: 0)

    the byte offset in the file to start reading from

Returns:

  • (String)

    the data read from the file

Raises:



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

def read(bytes: size, offset: 0)
  atomic_read_size = if bytes > tree.client.server_max_read_size
                       tree.client.server_max_read_size
                     else
                       bytes
                     end

  read_request = read_packet(read_length: atomic_read_size, offset: offset)
  raw_response = tree.client.send_recv(read_request)
  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.name
  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 < tree.client.server_max_read_size

    read_request = read_packet(read_length: atomic_read_size, offset: offset)
    raw_response = tree.client.send_recv(read_request)
    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.name
    end

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

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

Crafts the ReadRequest packet to be sent for read operations.

Parameters:

  • bytes (Integer)

    the number of bytes to read

  • offset (Integer) (defaults to: 0)

    the byte offset in the file to start reading from

Returns:



167
168
169
170
171
172
# File 'lib/ruby_smb/smb2/file.rb', line 167

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

#rename(new_file_name) ⇒ WindowsError::ErrorCode

Rename a file

Parameters:

  • new_file_name (String)

    the new name

Returns:

  • (WindowsError::ErrorCode)

    the NTStatus Response code

Raises:



325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/ruby_smb/smb2/file.rb', line 325

def rename(new_file_name)
  raw_response = tree.client.send_recv(rename_packet(new_file_name))
  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.

Parameters:

  • new_file_name (String)

    the new name

Returns:



343
344
345
346
347
348
# File 'lib/ruby_smb/smb2/file.rb', line 343

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



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/ruby_smb/smb2/file.rb', line 174

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)
  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
  if response.status_code == WindowsError::NTStatus::STATUS_PENDING
    sleep 1
    raw_response = tree.client.dispatcher.recv_packet
    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
  elsif response.status_code != WindowsError::NTStatus::STATUS_SUCCESS
    raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
  end
  response.buffer.to_binary_s
end

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



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/ruby_smb/smb2/file.rb', line 290

def send_recv_write(data:'', offset: 0)
  pkt = write_packet(data: data, offset: offset)
  raw_response = tree.client.send_recv(pkt)
  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
  if response.status_code == WindowsError::NTStatus::STATUS_PENDING
    sleep 1
    raw_response = tree.client.dispatcher.recv_packet
    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
  elsif response.status_code != WindowsError::NTStatus::STATUS_SUCCESS
    raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
  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.

Parameters:

Returns:



236
237
238
239
240
# File 'lib/ruby_smb/smb2/file.rb', line 236

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.

Parameters:

  • data (String) (defaults to: '')

    the data to write to the file

  • offset (Integer) (defaults to: 0)

    the offset in the file to start writing from

Returns:

  • (WindowsError::ErrorCode)

    the NTStatus code returned from the operation

Raises:



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 248

def write(data:'', offset: 0)
  buffer            = data.dup
  bytes             = data.length
  atomic_write_size = if bytes > tree.client.server_max_write_size
                        tree.client.server_max_write_size
                      else
                       bytes
                      end

  while buffer.length > 0 do
    write_request = write_packet(data: buffer.slice!(0,atomic_write_size), offset: offset)
    raw_response  = tree.client.send_recv(write_request)
    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) ⇒ Object

Creates the Request packet for the #write command

Parameters:

  • data (String) (defaults to: '')

    the data to write to the file

  • offset (Integer) (defaults to: 0)

    the offset in the file to start writing from

Returns:

  • []RubySMB::SMB2::Packet::WriteRequest] the request packet



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

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