Class: RubySMB::SMB1::File

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

Overview

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

Direct Known Subclasses

Pipe

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

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

Returns a new instance of File.

Raises:

  • (ArgumentError)


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/ruby_smb/smb1/file.rb', line 51

def initialize(tree:, response:, name:)
  raise ArgumentError, 'No tree provided' if tree.nil?
  raise ArgumentError, 'No response provided' if response.nil?
  raise ArgumentError, 'No file name provided' if name.nil?

  @tree = tree
  @name = name

  @attributes   = response.parameter_block.ext_file_attributes
  @fid          = response.parameter_block.fid
  @last_access  = response.parameter_block.last_access_time.to_datetime
  @last_change  = response.parameter_block.last_change_time.to_datetime
  @last_write   = response.parameter_block.last_write_time.to_datetime
  @size         = response.parameter_block.end_of_file
  @size_on_disk = response.parameter_block.allocation_size
end

Instance Attribute Details

#attributesRubySMB::SMB1::BitField::SmbExtFileAttributes



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

def attributes
  @attributes
end

#fidInteger

Returns:

  • (Integer)


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

def fid
  @fid
end

#last_accessDateTime

Returns:

  • (DateTime)


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

def last_access
  @last_access
end

#last_changeDateTime

Returns:

  • (DateTime)


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

def last_change
  @last_change
end

#last_writeDateTime

Returns:

  • (DateTime)


39
40
41
# File 'lib/ruby_smb/smb1/file.rb', line 39

def last_write
  @last_write
end

#nameString

Returns:

  • (String)


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

def name
  @name
end

#sizeInteger

Returns:

  • (Integer)


44
45
46
# File 'lib/ruby_smb/smb1/file.rb', line 44

def size
  @size
end

#size_on_diskInteger

Returns:

  • (Integer)


49
50
51
# File 'lib/ruby_smb/smb1/file.rb', line 49

def size_on_disk
  @size_on_disk
end

#treeRubySMB::SMB1::Tree

Returns:



9
10
11
# File 'lib/ruby_smb/smb1/file.rb', line 9

def tree
  @tree
end

Instance Method Details

#append(data:) ⇒ WindowsError::ErrorCode

Appends the supplied data to the end of the file.

Parameters:

  • data (String)

    the data to write to the file

Returns:

  • (WindowsError::ErrorCode)

    the NTStatus code returned from the operation



72
73
74
# File 'lib/ruby_smb/smb1/file.rb', line 72

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:



81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/ruby_smb/smb1/file.rb', line 81

def close
  close_request = set_header_fields(RubySMB::SMB1::Packet::CloseRequest.new)
  raw_response  = @tree.client.send_recv(close_request)
  response = RubySMB::SMB1::Packet::CloseResponse.read(raw_response)
  unless response.smb_header.command == RubySMB::SMB1::Commands::SMB_COM_CLOSE
    raise RubySMB::Error::InvalidPacket, 'Not a CloseResponse packet'
  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



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

def delete
  raw_response = @tree.client.send_recv(delete_packet)
  response = RubySMB::SMB1::Packet::Trans2::SetFileInformationResponse.read(raw_response)
  response.status_code
end

#delete_packetRubySMB::SMB1::Packet::Trans2::SetFileInformationRequest

Crafts the SetFileInformationRequest packet to be sent for delete operations.



177
178
179
180
181
182
183
184
185
186
# File 'lib/ruby_smb/smb1/file.rb', line 177

def delete_packet
  delete_request = RubySMB::SMB1::Packet::Trans2::SetFileInformationRequest.new
  delete_request = @tree.set_header_fields(delete_request)
  delete_request.data_block.trans2_parameters.fid = @fid
  passthrough_info_level = RubySMB::Fscc::FileInformation::FILE_DISPOSITION_INFORMATION +
    RubySMB::Fscc::FileInformation::SMB_INFO_PASSTHROUGH
  delete_request.data_block.trans2_parameters.information_level = passthrough_info_level
  delete_request.data_block.trans2_data.info_level_struct.delete_pending = 1
  set_trans2_params(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:



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
135
136
137
138
139
# File 'lib/ruby_smb/smb1/file.rb', line 103

def read(bytes: @size, offset: 0)
  atomic_read_size = if bytes > @tree.client.max_buffer_size
                       @tree.client.max_buffer_size
                     else
                       bytes
                     end
  remaining_bytes = bytes
  data = ''

  loop do
    read_request = read_packet(read_length: atomic_read_size, offset: offset)
    raw_response = @tree.client.send_recv(read_request)
    response = RubySMB::SMB1::Packet::ReadAndxResponse.read(raw_response)
    unless response.smb_header.command == RubySMB::SMB1::Commands::SMB_COM_READ_ANDX
      raise RubySMB::Error::InvalidPacket, 'Not a ReadAndxResponse packet'
    end
    unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
      raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
    end

    if response.is_a?(RubySMB::SMB1::Packet::ReadAndxResponse)
      data << response.data_block.data.to_binary_s
    else
      # Returns the current data immediately if we got an empty packet with an
      # SMB_COM_READ_ANDX command and a STATUS_SUCCESS (just in case)
      return data
    end

    remaining_bytes -= atomic_read_size
    break unless remaining_bytes > 0

    offset += atomic_read_size
    atomic_read_size = remaining_bytes if remaining_bytes < @tree.client.max_buffer_size
  end

  data
end

#read_packet(read_length: 0, offset: 0) ⇒ RubySMB::SMB1::Packet::ReadAndxRequest

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:



146
147
148
149
150
151
# File 'lib/ruby_smb/smb1/file.rb', line 146

def read_packet(read_length: 0, offset: 0)
  read_request = set_header_fields(RubySMB::SMB1::Packet::ReadAndxRequest.new)
  read_request.parameter_block.max_count_of_bytes_to_return = read_length
  read_request.parameter_block.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



251
252
253
254
255
# File 'lib/ruby_smb/smb1/file.rb', line 251

def rename(new_file_name)
  raw_response = tree.client.send_recv(rename_packet(new_file_name))
  response = RubySMB::SMB1::Packet::Trans2::SetFileInformationResponse.read(raw_response)
  response.status_code
end

#rename_packet(new_file_name) ⇒ RubySMB::SMB1::Packet::Trans2::SetFileInformationRequest

Crafts the SetFileInformationRequest packet to be sent for rename operations.

Parameters:

  • new_file_name (String)

    the new name

Returns:



261
262
263
264
265
266
267
268
269
270
# File 'lib/ruby_smb/smb1/file.rb', line 261

def rename_packet(new_file_name)
  rename_request = RubySMB::SMB1::Packet::Trans2::SetFileInformationRequest.new
  rename_request = @tree.set_header_fields(rename_request)
  rename_request.data_block.trans2_parameters.fid = @fid
  passthrough_info_level = RubySMB::Fscc::FileInformation::FILE_RENAME_INFORMATION +
    RubySMB::Fscc::FileInformation::SMB_INFO_PASSTHROUGH
  rename_request.data_block.trans2_parameters.information_level = passthrough_info_level
  rename_request.data_block.trans2_data.info_level_struct.file_name = new_file_name.encode('utf-16le')
  set_trans2_params(rename_request)
end

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



153
154
155
156
157
158
159
160
161
162
163
# File 'lib/ruby_smb/smb1/file.rb', line 153

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::SMB1::Packet::ReadAndxResponse.read(raw_response)
  unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
    raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
  end

  response.data_block.data.to_binary_s
end

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



239
240
241
242
243
244
245
# File 'lib/ruby_smb/smb1/file.rb', line 239

def send_recv_write(data:'', offset: 0)
  pkt = write_packet(data: data, offset: offset)
  pkt.set_64_bit_offset(true)
  raw_response = @tree.client.send_recv(pkt)
  response = RubySMB::SMB1::Packet::WriteAndxResponse.read(raw_response)
  response.parameter_block.count_low
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:



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

def set_header_fields(request)
  request = @tree.set_header_fields(request)
  request.parameter_block.fid = @fid
  request
end

#write(data:, offset: 0) ⇒ Integer

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

Parameters:

  • data (String)

    the data to write to the file

  • offset (Integer) (defaults to: 0)

    the offset in the file to start writing from

Returns:

  • (Integer)

    the count of bytes written

Raises:



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/ruby_smb/smb1/file.rb', line 195

def write(data:, offset: 0)
  buffer = data.dup
  bytes  = data.length
  total_bytes_written = 0

  loop do
    atomic_write_size = if bytes > @tree.client.max_buffer_size
                         @tree.client.max_buffer_size
                        else
                          bytes
                        end
    write_request = write_packet(data: buffer.slice!(0, atomic_write_size), offset: offset)
    raw_response = @tree.client.send_recv(write_request)
    response = RubySMB::SMB1::Packet::WriteAndxResponse.read(raw_response)
    unless response.smb_header.command == RubySMB::SMB1::Commands::SMB_COM_WRITE_ANDX
      raise RubySMB::Error::InvalidPacket, 'Not a WriteAndxResponse packet'
    end
    unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
      raise RubySMB::Error::UnexpectedStatusCode, response.status_code.name
    end
    bytes_written = response.parameter_block.count_low + (response.parameter_block.count_high << 16)
    total_bytes_written += bytes_written
    offset += bytes_written
    bytes -= bytes_written
    break unless buffer.length > 0
  end

  total_bytes_written
end

#write_packet(data: '', offset: 0) ⇒ RubySMB::SMB1::Packet::WriteAndxRequest

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:



230
231
232
233
234
235
236
237
# File 'lib/ruby_smb/smb1/file.rb', line 230

def write_packet(data:'', offset: 0)
  write_request = set_header_fields(RubySMB::SMB1::Packet::WriteAndxRequest.new)
  write_request.parameter_block.offset = offset
  write_request.parameter_block.write_mode.writethrough_mode = 1
  write_request.data_block.data = data
  write_request.parameter_block.remaining = write_request.parameter_block.data_length
  write_request
end