ruby_smb 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/lib/ruby_smb/client.rb +26 -4
- data/lib/ruby_smb/client/authentication.rb +43 -25
- data/lib/ruby_smb/client/echo.rb +20 -2
- data/lib/ruby_smb/client/negotiation.rb +27 -12
- data/lib/ruby_smb/client/tree_connect.rb +20 -14
- data/lib/ruby_smb/error.rb +40 -1
- data/lib/ruby_smb/generic_packet.rb +33 -4
- data/lib/ruby_smb/smb1/dcerpc.rb +7 -2
- data/lib/ruby_smb/smb1/file.rb +60 -11
- data/lib/ruby_smb/smb1/packet/close_request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/close_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/echo_request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/echo_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/empty_packet.rb +7 -0
- data/lib/ruby_smb/smb1/packet/logoff_request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/logoff_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/negotiate_request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/negotiate_response.rb +3 -7
- data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +4 -4
- data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/nt_create_andx_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/nt_trans/create_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/nt_trans/request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/nt_trans/response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/read_andx_request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/read_andx_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_request.rb +0 -1
- data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_response.rb +3 -2
- data/lib/ruby_smb/smb1/packet/trans/request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/trans/response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_response.rb +1 -1
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +8 -2
- data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +8 -2
- data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +2 -4
- data/lib/ruby_smb/smb1/packet/trans2/response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +2 -1
- data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +13 -3
- data/lib/ruby_smb/smb1/packet/tree_disconnect_request.rb +2 -4
- data/lib/ruby_smb/smb1/packet/tree_disconnect_response.rb +2 -1
- data/lib/ruby_smb/smb1/packet/write_andx_request.rb +2 -5
- data/lib/ruby_smb/smb1/packet/write_andx_response.rb +2 -1
- data/lib/ruby_smb/smb1/pipe.rb +8 -3
- data/lib/ruby_smb/smb1/tree.rb +40 -2
- data/lib/ruby_smb/smb2/dcerpc.rb +7 -2
- data/lib/ruby_smb/smb2/file.rb +97 -1
- data/lib/ruby_smb/smb2/packet/close_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/close_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/create_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/create_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/echo_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/echo_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/error_packet.rb +7 -0
- data/lib/ruby_smb/smb2/packet/ioctl_request.rb +2 -5
- data/lib/ruby_smb/smb2/packet/ioctl_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/logoff_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/logoff_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/negotiate_request.rb +2 -5
- data/lib/ruby_smb/smb2/packet/negotiate_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/query_directory_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/query_directory_response.rb +8 -2
- data/lib/ruby_smb/smb2/packet/read_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/read_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/session_setup_request.rb +2 -5
- data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/set_info_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/set_info_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +2 -5
- data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -2
- data/lib/ruby_smb/smb2/packet/tree_disconnect_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/tree_disconnect_response.rb +2 -1
- data/lib/ruby_smb/smb2/packet/write_request.rb +2 -4
- data/lib/ruby_smb/smb2/packet/write_response.rb +2 -1
- data/lib/ruby_smb/smb2/pipe.rb +9 -9
- data/lib/ruby_smb/smb2/tree.rb +44 -6
- data/lib/ruby_smb/version.rb +1 -1
- data/spec/lib/ruby_smb/client_spec.rb +123 -11
- data/spec/lib/ruby_smb/generic_packet_spec.rb +52 -4
- data/spec/lib/ruby_smb/smb1/file_spec.rb +182 -1
- data/spec/lib/ruby_smb/smb1/packet/{error_packet_spec.rb → empty_packet_spec.rb} +21 -0
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +11 -2
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +11 -2
- data/spec/lib/ruby_smb/smb1/packet/tree_connect_response_spec.rb +40 -0
- data/spec/lib/ruby_smb/smb1/pipe_spec.rb +63 -2
- data/spec/lib/ruby_smb/smb1/tree_spec.rb +44 -7
- data/spec/lib/ruby_smb/smb2/file_spec.rb +295 -2
- data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +51 -0
- data/spec/lib/ruby_smb/smb2/packet/query_directory_response_spec.rb +8 -0
- data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +8 -0
- data/spec/lib/ruby_smb/smb2/pipe_spec.rb +69 -3
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +214 -0
- metadata +6 -4
- metadata.gz.sig +0 -0
| @@ -99,7 +99,17 @@ RSpec.describe RubySMB::SMB2::File do | |
| 99 99 | 
             
              describe '#read' do
         | 
| 100 100 | 
             
                context 'for a small file' do
         | 
| 101 101 | 
             
                  let(:small_read) { file.read_packet(read_length: 108) }
         | 
| 102 | 
            -
                  let(:small_response) { | 
| 102 | 
            +
                  let(:small_response) {
         | 
| 103 | 
            +
                    response = RubySMB::SMB2::Packet::ReadResponse.new(data_length: 9, buffer: 'fake data')
         | 
| 104 | 
            +
                    response.smb2_header.command = RubySMB::SMB2::Commands::READ
         | 
| 105 | 
            +
                    response
         | 
| 106 | 
            +
                  }
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                  before :example do
         | 
| 109 | 
            +
                    allow(file).to receive(:read_packet)
         | 
| 110 | 
            +
                    allow(client).to receive(:send_recv)
         | 
| 111 | 
            +
                    allow(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).and_return(small_response)
         | 
| 112 | 
            +
                  end
         | 
| 103 113 |  | 
| 104 114 | 
             
                  it 'uses a single packet to read the entire file' do
         | 
| 105 115 | 
             
                    expect(file).to receive(:read_packet).with(read_length: 108, offset: 0).and_return(small_read)
         | 
| @@ -107,11 +117,35 @@ RSpec.describe RubySMB::SMB2::File do | |
| 107 117 | 
             
                    expect(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).with('fake data').and_return(small_response)
         | 
| 108 118 | 
             
                    expect(file.read).to eq 'fake data'
         | 
| 109 119 | 
             
                  end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                  context 'when the response is not valid' do
         | 
| 122 | 
            +
                    it 'raise an InvalidPacket exception' do
         | 
| 123 | 
            +
                      small_response.smb2_header.command = RubySMB::SMB2::Commands::LOGOFF
         | 
| 124 | 
            +
                      expect { file.read }.to raise_error(RubySMB::Error::InvalidPacket)
         | 
| 125 | 
            +
                    end
         | 
| 126 | 
            +
                  end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                  context 'when the response status code is not STATUS_SUCCESS' do
         | 
| 129 | 
            +
                    it 'raise an UnexpectedStatusCode exception' do
         | 
| 130 | 
            +
                      small_response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
         | 
| 131 | 
            +
                      expect { file.read }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
                  end
         | 
| 110 134 | 
             
                end
         | 
| 111 135 |  | 
| 112 136 | 
             
                context 'for a larger file' do
         | 
| 113 137 | 
             
                  let(:big_read) { file.read_packet(read_length: 108) }
         | 
| 114 | 
            -
                  let(:big_response) { | 
| 138 | 
            +
                  let(:big_response) {
         | 
| 139 | 
            +
                    response = RubySMB::SMB2::Packet::ReadResponse.new(data_length: 9, buffer: 'fake data')
         | 
| 140 | 
            +
                    response.smb2_header.command = RubySMB::SMB2::Commands::READ
         | 
| 141 | 
            +
                    response
         | 
| 142 | 
            +
                  }
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                  before :example do
         | 
| 145 | 
            +
                    allow(file).to receive(:read_packet)
         | 
| 146 | 
            +
                    allow(client).to receive(:send_recv)
         | 
| 147 | 
            +
                    allow(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).and_return(big_response)
         | 
| 148 | 
            +
                  end
         | 
| 115 149 |  | 
| 116 150 | 
             
                  it 'uses a multiple packet to read the file in chunks' do
         | 
| 117 151 | 
             
                    expect(file).to receive(:read_packet).once.with(read_length: described_class::MAX_PACKET_SIZE, offset: 0).and_return(big_read)
         | 
| @@ -120,6 +154,24 @@ RSpec.describe RubySMB::SMB2::File do | |
| 120 154 | 
             
                    expect(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).twice.with('fake data').and_return(big_response)
         | 
| 121 155 | 
             
                    file.read(bytes: (described_class::MAX_PACKET_SIZE * 2))
         | 
| 122 156 | 
             
                  end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                  context 'when the second response is not valid' do
         | 
| 159 | 
            +
                    it 'raise an InvalidPacket exception' do
         | 
| 160 | 
            +
                      allow(file).to receive(:read_packet).with(read_length: described_class::MAX_PACKET_SIZE, offset: described_class::MAX_PACKET_SIZE) do
         | 
| 161 | 
            +
                        big_response.smb2_header.command = RubySMB::SMB2::Commands::LOGOFF
         | 
| 162 | 
            +
                      end
         | 
| 163 | 
            +
                      expect { file.read(bytes: (described_class::MAX_PACKET_SIZE * 2)) }.to raise_error(RubySMB::Error::InvalidPacket)
         | 
| 164 | 
            +
                    end
         | 
| 165 | 
            +
                  end
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                  context 'when the second response status code is not STATUS_SUCCESS' do
         | 
| 168 | 
            +
                    it 'raise an UnexpectedStatusCode exception' do
         | 
| 169 | 
            +
                      allow(file).to receive(:read_packet).with(read_length: described_class::MAX_PACKET_SIZE, offset: described_class::MAX_PACKET_SIZE) do
         | 
| 170 | 
            +
                        big_response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
         | 
| 171 | 
            +
                      end
         | 
| 172 | 
            +
                      expect { file.read(bytes: (described_class::MAX_PACKET_SIZE * 2)) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
         | 
| 173 | 
            +
                    end
         | 
| 174 | 
            +
                  end
         | 
| 123 175 | 
             
                end
         | 
| 124 176 | 
             
              end
         | 
| 125 177 |  | 
| @@ -160,6 +212,13 @@ RSpec.describe RubySMB::SMB2::File do | |
| 160 212 | 
             
                    file.write(data: SecureRandom.random_bytes(described_class::MAX_PACKET_SIZE + 1))
         | 
| 161 213 | 
             
                  end
         | 
| 162 214 | 
             
                end
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                it 'raises an InvalidPacket exception if the response is not valid' do
         | 
| 217 | 
            +
                  allow(client).to receive(:send_recv)
         | 
| 218 | 
            +
                  allow(RubySMB::SMB2::Packet::WriteResponse).to receive(:read).and_return(write_response)
         | 
| 219 | 
            +
                  allow(write_response).to receive(:valid?).and_return(false)
         | 
| 220 | 
            +
                  expect { file.write(data: 'test') }.to raise_error(RubySMB::Error::InvalidPacket)
         | 
| 221 | 
            +
                end
         | 
| 163 222 | 
             
              end
         | 
| 164 223 |  | 
| 165 224 | 
             
              describe '#delete_packet' do
         | 
| @@ -193,6 +252,14 @@ RSpec.describe RubySMB::SMB2::File do | |
| 193 252 | 
             
                    expect(RubySMB::SMB2::Packet::SetInfoResponse).to receive(:read).with('raw_response').and_return(small_response)
         | 
| 194 253 | 
             
                    expect(file.delete).to eq WindowsError::NTStatus::STATUS_SUCCESS
         | 
| 195 254 | 
             
                  end
         | 
| 255 | 
            +
             | 
| 256 | 
            +
                  it 'raises an InvalidPacket exception if the response is not valid' do
         | 
| 257 | 
            +
                    allow(file).to receive(:delete_packet)
         | 
| 258 | 
            +
                    allow(client).to receive(:send_recv)
         | 
| 259 | 
            +
                    allow(RubySMB::SMB2::Packet::SetInfoResponse).to receive(:read).and_return(small_response)
         | 
| 260 | 
            +
                    allow(small_response).to receive(:valid?).and_return(false)
         | 
| 261 | 
            +
                    expect { file.delete }.to raise_error(RubySMB::Error::InvalidPacket)
         | 
| 262 | 
            +
                  end
         | 
| 196 263 | 
             
                end
         | 
| 197 264 | 
             
              end
         | 
| 198 265 |  | 
| @@ -228,6 +295,232 @@ RSpec.describe RubySMB::SMB2::File do | |
| 228 295 | 
             
                    expect(RubySMB::SMB2::Packet::SetInfoResponse).to receive(:read).with('raw_response').and_return(small_response)
         | 
| 229 296 | 
             
                    expect(file.rename('new_file.txt')).to eq WindowsError::NTStatus::STATUS_SUCCESS
         | 
| 230 297 | 
             
                  end
         | 
| 298 | 
            +
             | 
| 299 | 
            +
                  it 'raises an InvalidPacket exception if the response is not valid' do
         | 
| 300 | 
            +
                    allow(client).to receive(:send_recv)
         | 
| 301 | 
            +
                    allow(RubySMB::SMB2::Packet::SetInfoResponse).to receive(:read).and_return(small_response)
         | 
| 302 | 
            +
                    allow(small_response).to receive(:valid?).and_return(false)
         | 
| 303 | 
            +
                    expect { file.rename('new_file.txt') }.to raise_error(RubySMB::Error::InvalidPacket)
         | 
| 304 | 
            +
                  end
         | 
| 305 | 
            +
                end
         | 
| 306 | 
            +
              end
         | 
| 307 | 
            +
             | 
| 308 | 
            +
              describe '#close' do
         | 
| 309 | 
            +
                let(:request)  { double('CloseRequest') }
         | 
| 310 | 
            +
                let(:response) { double('CloseResponse') }
         | 
| 311 | 
            +
                let(:raw_response) { double('Raw response') }
         | 
| 312 | 
            +
             | 
| 313 | 
            +
                before :example do
         | 
| 314 | 
            +
                  allow(RubySMB::SMB2::Packet::CloseRequest).to receive(:new).and_return(request)
         | 
| 315 | 
            +
                  allow(file).to receive(:set_header_fields).and_return(request)
         | 
| 316 | 
            +
                  allow(client).to receive(:send_recv).and_return(raw_response)
         | 
| 317 | 
            +
                  allow(RubySMB::SMB2::Packet::CloseResponse).to receive(:read).and_return(response)
         | 
| 318 | 
            +
                  allow(response).to receive(:valid?).and_return(true)
         | 
| 319 | 
            +
                  allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_SUCCESS)
         | 
| 320 | 
            +
                end
         | 
| 321 | 
            +
             | 
| 322 | 
            +
                it 'creates a new SMB2 CloseRequest packet' do
         | 
| 323 | 
            +
                  expect(RubySMB::SMB2::Packet::CloseRequest).to receive(:new)
         | 
| 324 | 
            +
                  file.close
         | 
| 325 | 
            +
                end
         | 
| 326 | 
            +
             | 
| 327 | 
            +
                it 'calls Tree #set_header_fields to set SetFileInformationRequest headers' do
         | 
| 328 | 
            +
                  expect(file).to receive(:set_header_fields).with(request)
         | 
| 329 | 
            +
                  file.close
         | 
| 330 | 
            +
                end
         | 
| 331 | 
            +
             | 
| 332 | 
            +
                it 'calls Client #send_recv with the expected request' do
         | 
| 333 | 
            +
                  expect(client).to receive(:send_recv).with(request)
         | 
| 334 | 
            +
                  file.close
         | 
| 335 | 
            +
                end
         | 
| 336 | 
            +
             | 
| 337 | 
            +
                it 'parses the response as a SMB2 CloseResponse packet' do
         | 
| 338 | 
            +
                  expect(RubySMB::SMB2::Packet::CloseResponse).to receive(:read).with(raw_response)
         | 
| 339 | 
            +
                  file.close
         | 
| 340 | 
            +
                end
         | 
| 341 | 
            +
             | 
| 342 | 
            +
                it 'raises an InvalidPacket exception if the response is not valid' do
         | 
| 343 | 
            +
                  allow(response).to receive(:valid?).and_return(false)
         | 
| 344 | 
            +
                  smb2_header = double('SMB2 Header')
         | 
| 345 | 
            +
                  allow(response).to receive(:smb2_header).and_return(smb2_header)
         | 
| 346 | 
            +
                  allow(smb2_header).to receive_messages(:protocol => nil, :command => nil)
         | 
| 347 | 
            +
                  expect { file.close }.to raise_error(RubySMB::Error::InvalidPacket)
         | 
| 348 | 
            +
                end
         | 
| 349 | 
            +
             | 
| 350 | 
            +
                it 'raises an UnexpectedStatusCode exception if the response status code is not STATUS_SUCCESS' do
         | 
| 351 | 
            +
                  allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND)
         | 
| 352 | 
            +
                  expect { file.close }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
         | 
| 353 | 
            +
                end
         | 
| 354 | 
            +
             | 
| 355 | 
            +
                it 'returns the response status code' do
         | 
| 356 | 
            +
                  expect(file.close).to eq WindowsError::NTStatus::STATUS_SUCCESS
         | 
| 357 | 
            +
                end
         | 
| 358 | 
            +
              end
         | 
| 359 | 
            +
             | 
| 360 | 
            +
              describe '#send_recv_read' do
         | 
| 361 | 
            +
                let(:read_data) { 'read data' }
         | 
| 362 | 
            +
                let(:raw_response) { double('fake raw response data') }
         | 
| 363 | 
            +
                let(:read_response) {
         | 
| 364 | 
            +
                  res = RubySMB::SMB2::Packet::ReadResponse.new
         | 
| 365 | 
            +
                  res.data_length = read_data.size
         | 
| 366 | 
            +
                  res.buffer = read_data
         | 
| 367 | 
            +
                  res
         | 
| 368 | 
            +
                }
         | 
| 369 | 
            +
             | 
| 370 | 
            +
                before :example do
         | 
| 371 | 
            +
                  allow(client).to receive(:send_recv).and_return(raw_response)
         | 
| 372 | 
            +
                  allow(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).with(raw_response).and_return(read_response)
         | 
| 373 | 
            +
                end
         | 
| 374 | 
            +
             | 
| 375 | 
            +
                context 'when the number of bytes to read is not provided' do
         | 
| 376 | 
            +
                  it 'reads 0 bytes by default' do
         | 
| 377 | 
            +
                    expect(file).to receive(:read_packet).with(read_length: 0, offset: 0).once.and_call_original
         | 
| 378 | 
            +
                    file.send_recv_read
         | 
| 379 | 
            +
                  end
         | 
| 380 | 
            +
                end
         | 
| 381 | 
            +
             | 
| 382 | 
            +
                it 'only reads the number of bytes provided as argument' do
         | 
| 383 | 
            +
                  bytes = 5
         | 
| 384 | 
            +
                  expect(file).to receive(:read_packet).with(read_length: bytes, offset: 0).once.and_call_original
         | 
| 385 | 
            +
                  file.send_recv_read(read_length: bytes)
         | 
| 386 | 
            +
                end
         | 
| 387 | 
            +
             | 
| 388 | 
            +
                it 'reads from the offset provided as argument' do
         | 
| 389 | 
            +
                  offset = 3
         | 
| 390 | 
            +
                  expect(file).to receive(:read_packet).with(read_length: 0, offset: offset).once.and_call_original
         | 
| 391 | 
            +
                  file.send_recv_read(offset: offset)
         | 
| 392 | 
            +
                end
         | 
| 393 | 
            +
             | 
| 394 | 
            +
                it 'calls Client #send_recv with the expected request' do
         | 
| 395 | 
            +
                  request = double('Request')
         | 
| 396 | 
            +
                  allow(file).to receive(:read_packet).and_return(request)
         | 
| 397 | 
            +
                  expect(client).to receive(:send_recv).with(request)
         | 
| 398 | 
            +
                  file.send_recv_read
         | 
| 399 | 
            +
                end
         | 
| 400 | 
            +
             | 
| 401 | 
            +
                it 'parses the response as a SMB2 ReadResponse packet' do
         | 
| 402 | 
            +
                  expect(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).with(raw_response)
         | 
| 403 | 
            +
                  file.send_recv_read
         | 
| 404 | 
            +
                end
         | 
| 405 | 
            +
             | 
| 406 | 
            +
                it 'raises an InvalidPacket exception if the response is not valid' do
         | 
| 407 | 
            +
                  allow(read_response).to receive(:valid?).and_return(false)
         | 
| 408 | 
            +
                  expect { file.send_recv_read }.to raise_error(RubySMB::Error::InvalidPacket)
         | 
| 409 | 
            +
                end
         | 
| 410 | 
            +
             | 
| 411 | 
            +
                context 'when the response status code is STATUS_PENDING' do
         | 
| 412 | 
            +
                  before :example do
         | 
| 413 | 
            +
                    allow(file).to receive(:sleep)
         | 
| 414 | 
            +
                    allow(read_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_PENDING)
         | 
| 415 | 
            +
                    allow(dispatcher).to receive(:recv_packet).and_return(raw_response)
         | 
| 416 | 
            +
                  end
         | 
| 417 | 
            +
             | 
| 418 | 
            +
                  it 'wait 1 second and calls Client dispatcher #recv_packet method one more time' do
         | 
| 419 | 
            +
                    expect(file).to receive(:sleep).with(1)
         | 
| 420 | 
            +
                    expect(dispatcher).to receive(:recv_packet)
         | 
| 421 | 
            +
                    file.send_recv_read
         | 
| 422 | 
            +
                  end
         | 
| 423 | 
            +
             | 
| 424 | 
            +
                  it 'parses the response as a SMB2 ReadResponse packet' do
         | 
| 425 | 
            +
                    expect(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).twice.with(raw_response)
         | 
| 426 | 
            +
                    file.send_recv_read
         | 
| 427 | 
            +
                  end
         | 
| 428 | 
            +
             | 
| 429 | 
            +
                  it 'raises an InvalidPacket exception if the response is not valid' do
         | 
| 430 | 
            +
                    allow(dispatcher).to receive(:recv_packet) do
         | 
| 431 | 
            +
                      allow(read_response).to receive(:valid?).and_return(false)
         | 
| 432 | 
            +
                      raw_response
         | 
| 433 | 
            +
                    end
         | 
| 434 | 
            +
                    expect { file.send_recv_read }.to raise_error(RubySMB::Error::InvalidPacket)
         | 
| 435 | 
            +
                  end
         | 
| 436 | 
            +
                end
         | 
| 437 | 
            +
             | 
| 438 | 
            +
                it 'raises an UnexpectedStatusCode exception if the response status code is not STATUS_SUCCESS' do
         | 
| 439 | 
            +
                  allow(read_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND)
         | 
| 440 | 
            +
                  expect { file.send_recv_read }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
         | 
| 441 | 
            +
                end
         | 
| 442 | 
            +
             | 
| 443 | 
            +
                it 'returns the expected string' do
         | 
| 444 | 
            +
                  expect(file.send_recv_read).to eq(read_data)
         | 
| 445 | 
            +
                end
         | 
| 446 | 
            +
              end
         | 
| 447 | 
            +
             | 
| 448 | 
            +
              describe '#send_recv_write' do
         | 
| 449 | 
            +
                let(:write_data) { 'write data' }
         | 
| 450 | 
            +
                let(:request) { double('Request') }
         | 
| 451 | 
            +
                let(:raw_response) { double('fake raw response data') }
         | 
| 452 | 
            +
                let(:write_response) {
         | 
| 453 | 
            +
                  res = RubySMB::SMB2::Packet::WriteResponse.new
         | 
| 454 | 
            +
                  res.write_count = write_data.size
         | 
| 455 | 
            +
                  res
         | 
| 456 | 
            +
                }
         | 
| 457 | 
            +
             | 
| 458 | 
            +
                before :example do
         | 
| 459 | 
            +
                  allow(file).to receive(:write_packet).and_return(request)
         | 
| 460 | 
            +
                  allow(client).to receive(:send_recv).and_return(raw_response)
         | 
| 461 | 
            +
                  allow(RubySMB::SMB2::Packet::WriteResponse).to receive(:read).with(raw_response).and_return(write_response)
         | 
| 462 | 
            +
                end
         | 
| 463 | 
            +
             | 
| 464 | 
            +
                it 'reads 0 bytes from offset 0 by default' do
         | 
| 465 | 
            +
                  expect(file).to receive(:write_packet).with(data: '', offset: 0).once.and_call_original
         | 
| 466 | 
            +
                  file.send_recv_write
         | 
| 467 | 
            +
                end
         | 
| 468 | 
            +
             | 
| 469 | 
            +
                it 'writes the data provided as argument' do
         | 
| 470 | 
            +
                  expect(file).to receive(:write_packet).with(data: write_data, offset: 0).once.and_call_original
         | 
| 471 | 
            +
                  file.send_recv_write(data: write_data)
         | 
| 472 | 
            +
                end
         | 
| 473 | 
            +
             | 
| 474 | 
            +
                it 'reads from the offset provided as argument' do
         | 
| 475 | 
            +
                  offset = 3
         | 
| 476 | 
            +
                  expect(file).to receive(:write_packet).with(data: '', offset: offset).once.and_call_original
         | 
| 477 | 
            +
                  file.send_recv_write(offset: offset)
         | 
| 478 | 
            +
                end
         | 
| 479 | 
            +
             | 
| 480 | 
            +
                it 'calls Client #send_recv with the expected request' do
         | 
| 481 | 
            +
                  expect(client).to receive(:send_recv).with(request)
         | 
| 482 | 
            +
                  file.send_recv_write
         | 
| 483 | 
            +
                end
         | 
| 484 | 
            +
             | 
| 485 | 
            +
                it 'parses the response as a SMB1 WriteResponse packet' do
         | 
| 486 | 
            +
                  expect(RubySMB::SMB2::Packet::WriteResponse).to receive(:read).with(raw_response)
         | 
| 487 | 
            +
                  file.send_recv_write
         | 
| 488 | 
            +
                end
         | 
| 489 | 
            +
             | 
| 490 | 
            +
                context 'when the response status code is STATUS_PENDING' do
         | 
| 491 | 
            +
                  before :example do
         | 
| 492 | 
            +
                    allow(file).to receive(:sleep)
         | 
| 493 | 
            +
                    allow(write_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_PENDING)
         | 
| 494 | 
            +
                    allow(dispatcher).to receive(:recv_packet).and_return(raw_response)
         | 
| 495 | 
            +
                  end
         | 
| 496 | 
            +
             | 
| 497 | 
            +
                  it 'wait 1 second and calls Client dispatcher #recv_packet method one more time' do
         | 
| 498 | 
            +
                    expect(file).to receive(:sleep).with(1)
         | 
| 499 | 
            +
                    expect(dispatcher).to receive(:recv_packet)
         | 
| 500 | 
            +
                    file.send_recv_write
         | 
| 501 | 
            +
                  end
         | 
| 502 | 
            +
             | 
| 503 | 
            +
                  it 'parses the response as a SMB2 WriteResponse packet' do
         | 
| 504 | 
            +
                    expect(RubySMB::SMB2::Packet::WriteResponse).to receive(:read).twice.with(raw_response)
         | 
| 505 | 
            +
                    file.send_recv_write
         | 
| 506 | 
            +
                  end
         | 
| 507 | 
            +
             | 
| 508 | 
            +
                  it 'raises an InvalidPacket exception if the response is not valid' do
         | 
| 509 | 
            +
                    allow(dispatcher).to receive(:recv_packet) do
         | 
| 510 | 
            +
                      allow(write_response).to receive(:valid?).and_return(false)
         | 
| 511 | 
            +
                      raw_response
         | 
| 512 | 
            +
                    end
         | 
| 513 | 
            +
                    expect { file.send_recv_write }.to raise_error(RubySMB::Error::InvalidPacket)
         | 
| 514 | 
            +
                  end
         | 
| 515 | 
            +
                end
         | 
| 516 | 
            +
             | 
| 517 | 
            +
                it 'raises an InvalidPacket exception if the response is not valid' do
         | 
| 518 | 
            +
                  allow(write_response).to receive(:valid?).and_return(false)
         | 
| 519 | 
            +
                  expect { file.send_recv_write }.to raise_error(RubySMB::Error::InvalidPacket)
         | 
| 520 | 
            +
                end
         | 
| 521 | 
            +
             | 
| 522 | 
            +
                it 'returns the expected response #write_count value' do
         | 
| 523 | 
            +
                  expect(file.send_recv_write).to eq(write_data.size)
         | 
| 231 524 | 
             
                end
         | 
| 232 525 | 
             
              end
         | 
| 233 526 | 
             
            end
         | 
| @@ -0,0 +1,51 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            RSpec.describe RubySMB::SMB2::Packet::ErrorPacket do
         | 
| 4 | 
            +
              subject(:packet) { described_class.new }
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              it { is_expected.to respond_to :smb2_header }
         | 
| 7 | 
            +
              it { is_expected.to respond_to :structure_size }
         | 
| 8 | 
            +
              it { is_expected.to respond_to :error_data }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              describe '#smb2_header' do
         | 
| 11 | 
            +
                subject(:header) { packet.smb2_header }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                it 'is a standard SMB2 Header' do
         | 
| 14 | 
            +
                  expect(header).to be_a RubySMB::SMB2::SMB2Header
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              describe '#structure_size' do
         | 
| 19 | 
            +
                it 'should be a 16-bit unsigned integer' do
         | 
| 20 | 
            +
                  expect(packet.structure_size).to be_a BinData::Uint16le
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              describe '#error_data' do
         | 
| 25 | 
            +
                it 'should be a 8-bit unsigned integer' do
         | 
| 26 | 
            +
                  expect(packet.error_data).to be_a BinData::Uint8
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              describe '#valid?' do
         | 
| 31 | 
            +
                before :example do
         | 
| 32 | 
            +
                  packet.original_command = RubySMB::SMB2::Commands::LOGOFF
         | 
| 33 | 
            +
                  packet.smb2_header.command = RubySMB::SMB2::Commands::LOGOFF
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                it 'returns true if the packet protocol ID and header command are valid' do
         | 
| 37 | 
            +
                  expect(packet).to be_valid
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                it 'returns false if the packet protocol ID is wrong' do
         | 
| 41 | 
            +
                  packet.smb2_header.protocol = RubySMB::SMB1::SMB_PROTOCOL_ID
         | 
| 42 | 
            +
                  expect(packet).to_not be_valid
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                it 'returns false if the packet header command is wrong' do
         | 
| 46 | 
            +
                  packet.smb2_header.command = RubySMB::SMB2::Commands::NEGOTIATE
         | 
| 47 | 
            +
                  expect(packet).to_not be_valid
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
            end
         | 
| 51 | 
            +
             | 
| @@ -60,5 +60,13 @@ RSpec.describe RubySMB::SMB2::Packet::QueryDirectoryResponse do | |
| 60 60 | 
             
                  packet.buffer = names_blob
         | 
| 61 61 | 
             
                  expect(packet.results(RubySMB::Fscc::FileInformation::FileNamesInformation)).to eq names_array
         | 
| 62 62 | 
             
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                context 'when the File Information is not a valid' do
         | 
| 65 | 
            +
                  it 'raises an InvalidPacket exception' do
         | 
| 66 | 
            +
                    packet.buffer = names_blob
         | 
| 67 | 
            +
                    allow(RubySMB::Fscc::FileInformation::FileNamesInformation).to receive(:read).and_raise(IOError)
         | 
| 68 | 
            +
                    expect { packet.results(RubySMB::Fscc::FileInformation::FileNamesInformation) }.to raise_error(RubySMB::Error::InvalidPacket)
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                end
         | 
| 63 71 | 
             
              end
         | 
| 64 72 | 
             
            end
         | 
| @@ -52,5 +52,13 @@ RSpec.describe RubySMB::SMB2::Packet::TreeConnectResponse do | |
| 52 52 | 
             
                  allow(packet).to receive(:is_directory?).and_return(false)
         | 
| 53 53 | 
             
                  expect(packet.access_rights).to be_a RubySMB::SMB2::BitField::FileAccessMask
         | 
| 54 54 | 
             
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                context 'when it is not a valid FileAccessMask' do
         | 
| 57 | 
            +
                  it 'raises an InvalidBitField exception' do
         | 
| 58 | 
            +
                    allow(packet).to receive(:is_directory?).and_return(false)
         | 
| 59 | 
            +
                    allow(RubySMB::SMB2::BitField::FileAccessMask).to receive(:read).and_raise(IOError)
         | 
| 60 | 
            +
                    expect { packet.access_rights }.to raise_error(RubySMB::Error::InvalidBitField)
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end
         | 
| 55 63 | 
             
              end
         | 
| 56 64 | 
             
            end
         | 
| @@ -36,6 +36,72 @@ RSpec.describe RubySMB::SMB2::Pipe do | |
| 36 36 |  | 
| 37 37 | 
             
              subject(:pipe) { described_class.new(name: 'msf-pipe', response: create_response, tree: tree) }
         | 
| 38 38 |  | 
| 39 | 
            +
              describe '#peek' do
         | 
| 40 | 
            +
                let(:request) { RubySMB::SMB2::Packet::IoctlRequest.new }
         | 
| 41 | 
            +
                let(:raw_response) { double('Raw response') }
         | 
| 42 | 
            +
                let(:response) { double('Response') }
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                before :example do
         | 
| 45 | 
            +
                  allow(RubySMB::SMB2::Packet::IoctlRequest).to receive(:new).and_return(request)
         | 
| 46 | 
            +
                  allow(client).to receive(:send_recv).and_return(raw_response)
         | 
| 47 | 
            +
                  allow(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).and_return(response)
         | 
| 48 | 
            +
                  allow(response).to receive(:valid?).and_return(true)
         | 
| 49 | 
            +
                  allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_SUCCESS)
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                it 'creates a IoctlRequest'do
         | 
| 53 | 
            +
                  expect(RubySMB::SMB2::Packet::IoctlRequest).to receive(:new)
         | 
| 54 | 
            +
                  pipe.peek
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                it 'sets the request #ctl_code field' do
         | 
| 58 | 
            +
                  expect(request).to receive(:ctl_code=).with(RubySMB::Fscc::ControlCodes::FSCTL_PIPE_PEEK)
         | 
| 59 | 
            +
                  pipe.peek
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                it 'sets the request #is_fsctl flag to true' do
         | 
| 63 | 
            +
                  pipe.peek
         | 
| 64 | 
            +
                  expect(request.flags.is_fsctl).to eq 1
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                it 'sets the request #max_output_response field to the expected value' do
         | 
| 68 | 
            +
                  pipe.peek(peek_size: 10)
         | 
| 69 | 
            +
                  expect(request.max_output_response).to eq(16 + 10)
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                it 'calls #set_header_fields' do
         | 
| 73 | 
            +
                  expect(pipe).to receive(:set_header_fields).with(request)
         | 
| 74 | 
            +
                  pipe.peek
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                it 'calls Client #send_recv' do
         | 
| 78 | 
            +
                  expect(client).to receive(:send_recv).with(request)
         | 
| 79 | 
            +
                  pipe.peek
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                it 'parses the response as a SMB2 IoctlResponse packet' do
         | 
| 83 | 
            +
                  expect(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).with(raw_response)
         | 
| 84 | 
            +
                  pipe.peek
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                it 'raises an InvalidPacket exception if the response is not valid' do
         | 
| 88 | 
            +
                  allow(response).to receive(:valid?).and_return(false)
         | 
| 89 | 
            +
                  smb2_header = double('SMB2 Header')
         | 
| 90 | 
            +
                  allow(response).to receive(:smb2_header).and_return(smb2_header)
         | 
| 91 | 
            +
                  allow(smb2_header).to receive_messages(:protocol => nil, :command => nil)
         | 
| 92 | 
            +
                  expect { pipe.peek }.to raise_error(RubySMB::Error::InvalidPacket)
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                it 'raises an UnexpectedStatusCode exception if the response status code is not STATUS_SUCCESS or STATUS_BUFFER_OVERFLOW' do
         | 
| 96 | 
            +
                  allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND)
         | 
| 97 | 
            +
                  expect { pipe.peek }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                it 'returns the expected response' do
         | 
| 101 | 
            +
                  expect(pipe.peek).to eq(response)
         | 
| 102 | 
            +
                end
         | 
| 103 | 
            +
              end
         | 
| 104 | 
            +
             | 
| 39 105 | 
             
              describe '#peek_available' do
         | 
| 40 106 | 
             
                it 'reads the correct number of bytes available' do
         | 
| 41 107 | 
             
                  allow(pipe).to receive(:peek) { ioctl_response }
         | 
| @@ -244,9 +310,9 @@ RSpec.describe RubySMB::SMB2::Pipe do | |
| 244 310 | 
             
                    pipe.ioctl_send_recv(action, options)
         | 
| 245 311 | 
             
                  end
         | 
| 246 312 |  | 
| 247 | 
            -
                  it 'raises the expected exception when it is not a  | 
| 248 | 
            -
                     | 
| 249 | 
            -
                    allow(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).and_return( | 
| 313 | 
            +
                  it 'raises the expected exception when it is not a valid packet' do
         | 
| 314 | 
            +
                    ioctl_response.smb2_header.command = RubySMB::SMB2::Commands::LOGOFF
         | 
| 315 | 
            +
                    allow(RubySMB::SMB2::Packet::IoctlResponse).to receive(:read).and_return(ioctl_response)
         | 
| 250 316 | 
             
                    expect { pipe.ioctl_send_recv(action, options) }.to raise_error(RubySMB::Error::InvalidPacket)
         | 
| 251 317 | 
             
                  end
         | 
| 252 318 |  |