ruby_smb 2.0.1 → 2.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -1
  4. data/examples/anonymous_auth.rb +3 -3
  5. data/examples/append_file.rb +10 -8
  6. data/examples/authenticate.rb +9 -5
  7. data/examples/delete_file.rb +8 -6
  8. data/examples/enum_registry_key.rb +5 -4
  9. data/examples/enum_registry_values.rb +5 -4
  10. data/examples/list_directory.rb +8 -6
  11. data/examples/negotiate_with_netbios_service.rb +9 -5
  12. data/examples/net_share_enum_all.rb +6 -4
  13. data/examples/pipes.rb +11 -12
  14. data/examples/query_service_status.rb +64 -0
  15. data/examples/read_file.rb +8 -6
  16. data/examples/read_registry_key_value.rb +6 -5
  17. data/examples/rename_file.rb +9 -7
  18. data/examples/tree_connect.rb +7 -5
  19. data/examples/write_file.rb +9 -7
  20. data/lib/ruby_smb/client.rb +81 -48
  21. data/lib/ruby_smb/client/authentication.rb +5 -10
  22. data/lib/ruby_smb/client/echo.rb +2 -4
  23. data/lib/ruby_smb/client/negotiation.rb +21 -14
  24. data/lib/ruby_smb/client/tree_connect.rb +2 -4
  25. data/lib/ruby_smb/client/utils.rb +16 -10
  26. data/lib/ruby_smb/client/winreg.rb +1 -1
  27. data/lib/ruby_smb/dcerpc.rb +4 -0
  28. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  29. data/lib/ruby_smb/dcerpc/ndr.rb +306 -44
  30. data/lib/ruby_smb/dcerpc/netlogon.rb +101 -0
  31. data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request.rb +28 -0
  32. data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_response.rb +26 -0
  33. data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request.rb +27 -0
  34. data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_response.rb +23 -0
  35. data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request.rb +25 -0
  36. data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response.rb +24 -0
  37. data/lib/ruby_smb/dcerpc/request.rb +19 -0
  38. data/lib/ruby_smb/dcerpc/rpc_security_attributes.rb +34 -0
  39. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +9 -6
  40. data/lib/ruby_smb/dcerpc/svcctl.rb +479 -0
  41. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request.rb +48 -0
  42. data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response.rb +26 -0
  43. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request.rb +25 -0
  44. data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response.rb +26 -0
  45. data/lib/ruby_smb/dcerpc/svcctl/control_service_request.rb +26 -0
  46. data/lib/ruby_smb/dcerpc/svcctl/control_service_response.rb +26 -0
  47. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request.rb +35 -0
  48. data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response.rb +23 -0
  49. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_request.rb +31 -0
  50. data/lib/ruby_smb/dcerpc/svcctl/open_service_w_response.rb +23 -0
  51. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request.rb +25 -0
  52. data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response.rb +44 -0
  53. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_request.rb +23 -0
  54. data/lib/ruby_smb/dcerpc/svcctl/query_service_status_response.rb +27 -0
  55. data/lib/ruby_smb/dcerpc/svcctl/service_status.rb +25 -0
  56. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_request.rb +27 -0
  57. data/lib/ruby_smb/dcerpc/svcctl/start_service_w_response.rb +25 -0
  58. data/lib/ruby_smb/dcerpc/winreg.rb +98 -17
  59. data/lib/ruby_smb/dcerpc/winreg/create_key_request.rb +73 -0
  60. data/lib/ruby_smb/dcerpc/winreg/create_key_response.rb +36 -0
  61. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +1 -1
  62. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +1 -1
  63. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +1 -1
  64. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +4 -4
  65. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +1 -1
  66. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +7 -6
  67. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +10 -10
  68. data/lib/ruby_smb/dcerpc/winreg/save_key_request.rb +37 -0
  69. data/lib/ruby_smb/dcerpc/winreg/save_key_response.rb +23 -0
  70. data/lib/ruby_smb/dispatcher/base.rb +1 -1
  71. data/lib/ruby_smb/dispatcher/socket.rb +1 -1
  72. data/lib/ruby_smb/error.rb +21 -5
  73. data/lib/ruby_smb/field/stringz16.rb +17 -1
  74. data/lib/ruby_smb/generic_packet.rb +11 -1
  75. data/lib/ruby_smb/nbss/session_header.rb +4 -4
  76. data/lib/ruby_smb/smb1/file.rb +10 -25
  77. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +0 -1
  78. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +0 -1
  79. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +1 -2
  80. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +1 -13
  81. data/lib/ruby_smb/smb1/pipe.rb +8 -6
  82. data/lib/ruby_smb/smb1/tree.rb +13 -9
  83. data/lib/ruby_smb/smb2/file.rb +33 -33
  84. data/lib/ruby_smb/smb2/pipe.rb +9 -6
  85. data/lib/ruby_smb/smb2/tree.rb +21 -11
  86. data/lib/ruby_smb/version.rb +1 -1
  87. data/spec/lib/ruby_smb/client_spec.rb +195 -101
  88. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +1396 -77
  89. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request_spec.rb +69 -0
  90. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_response_spec.rb +53 -0
  91. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request_spec.rb +69 -0
  92. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_response_spec.rb +37 -0
  93. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request_spec.rb +45 -0
  94. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response_spec.rb +37 -0
  95. data/spec/lib/ruby_smb/dcerpc/rpc_security_attributes_spec.rb +161 -0
  96. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +49 -12
  97. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request_spec.rb +191 -0
  98. data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response_spec.rb +38 -0
  99. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request_spec.rb +30 -0
  100. data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response_spec.rb +38 -0
  101. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_request_spec.rb +39 -0
  102. data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_response_spec.rb +38 -0
  103. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request_spec.rb +78 -0
  104. data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response_spec.rb +38 -0
  105. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_request_spec.rb +59 -0
  106. data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_response_spec.rb +38 -0
  107. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request_spec.rb +38 -0
  108. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response_spec.rb +152 -0
  109. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_request_spec.rb +30 -0
  110. data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_response_spec.rb +38 -0
  111. data/spec/lib/ruby_smb/dcerpc/svcctl/service_status_spec.rb +72 -0
  112. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_request_spec.rb +46 -0
  113. data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_response_spec.rb +30 -0
  114. data/spec/lib/ruby_smb/dcerpc/svcctl_spec.rb +512 -0
  115. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_request_spec.rb +110 -0
  116. data/spec/lib/ruby_smb/dcerpc/winreg/create_key_response_spec.rb +44 -0
  117. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +0 -4
  118. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +2 -2
  119. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +2 -2
  120. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +9 -4
  121. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +0 -4
  122. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +17 -17
  123. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +11 -23
  124. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_request_spec.rb +57 -0
  125. data/spec/lib/ruby_smb/dcerpc/winreg/save_key_response_spec.rb +22 -0
  126. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +227 -41
  127. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +10 -10
  128. data/spec/lib/ruby_smb/error_spec.rb +34 -5
  129. data/spec/lib/ruby_smb/field/stringz16_spec.rb +12 -0
  130. data/spec/lib/ruby_smb/generic_packet_spec.rb +7 -0
  131. data/spec/lib/ruby_smb/nbss/session_header_spec.rb +4 -11
  132. data/spec/lib/ruby_smb/smb1/file_spec.rb +2 -4
  133. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +0 -1
  134. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +0 -1
  135. data/spec/lib/ruby_smb/smb1/packet/trans2/open2_response_spec.rb +0 -5
  136. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +0 -6
  137. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +30 -5
  138. data/spec/lib/ruby_smb/smb1/tree_spec.rb +22 -0
  139. data/spec/lib/ruby_smb/smb2/file_spec.rb +61 -9
  140. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +9 -5
  141. data/spec/lib/ruby_smb/smb2/tree_spec.rb +58 -1
  142. metadata +91 -2
  143. metadata.gz.sig +0 -0
@@ -9,16 +9,45 @@ RSpec.describe RubySMB::Error::InvalidPacket do
9
9
  end
10
10
 
11
11
  context 'with a Hash' do
12
- it 'outputs the expected error message' do
13
- ex = described_class.new(
12
+ let(:ex) do
13
+ described_class.new(
14
14
  expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
15
15
  expected_cmd: RubySMB::SMB1::Packet::NegotiateResponseExtended::COMMAND,
16
16
  expected_custom: "extended_security=1",
17
- received_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
18
- received_cmd: RubySMB::SMB2::Packet::NegotiateResponse::COMMAND,
17
+ packet: packet,
19
18
  received_custom: "extended_security=0"
20
19
  )
21
- expect(ex.to_s).to eq('Expecting SMB1 protocol with command=114 (extended_security=1), got SMB2 protocol with command=0 (extended_security=0)')
20
+ end
21
+
22
+ context 'with an SMB2 packet' do
23
+ let(:packet) { RubySMB::SMB2::Packet::NegotiateResponse.new }
24
+
25
+ it 'outputs the expected error message' do
26
+ expect(ex.to_s).to eq('Expecting SMB1 protocol with command=114 (extended_security=1), got SMB2 protocol with command=0 (extended_security=0), Status: (0x00000000) STATUS_SUCCESS: The operation completed successfully.')
27
+ end
28
+ end
29
+
30
+ context 'with an SMB1 packet' do
31
+ let(:packet) { RubySMB::SMB1::Packet::ReadAndxRequest.new }
32
+
33
+ it 'outputs the expected error message' do
34
+ expect(ex.to_s).to eq('Expecting SMB1 protocol with command=114 (extended_security=1), got SMB1 protocol with command=46 (extended_security=0), Status: (0x00000000) STATUS_SUCCESS: The operation completed successfully.')
35
+ end
36
+ end
37
+
38
+ context 'without packet' do
39
+ let(:ex) do
40
+ described_class.new(
41
+ expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
42
+ expected_cmd: RubySMB::SMB1::Packet::NegotiateResponseExtended::COMMAND,
43
+ expected_custom: "extended_security=1",
44
+ received_custom: "extended_security=0"
45
+ )
46
+ end
47
+
48
+ it 'outputs the expected error message' do
49
+ expect(ex.to_s).to eq('Expecting SMB1 protocol with command=114 (extended_security=1), got ??? protocol with command=??? (extended_security=0)')
50
+ end
22
51
  end
23
52
  end
24
53
 
@@ -44,5 +44,17 @@ RSpec.describe RubySMB::Field::Stringz16 do
44
44
  io = StringIO.new("A\x00B\x00C\x00D\x00")
45
45
  expect { stringz16.read(io) }.to raise_error(EOFError)
46
46
  end
47
+
48
+ it 'trims the string to #max_length and makes sure it ends with a null terminator' do
49
+ io = StringIO.new("A\x00B\x00C\x00D\x00")
50
+ str = described_class.new(max_length: 6)
51
+ expect(str.read(io).to_binary_s).to eq("A\x00B\x00\x00\x00".b)
52
+ end
53
+
54
+ it 'raises an exception when #max_length is not a multiple of two' do
55
+ io = StringIO.new("A\x00B\x00C\x00D\x00")
56
+ str = described_class.new(max_length: 5)
57
+ expect { str.read(io) }.to raise_error(ArgumentError)
58
+ end
47
59
  end
48
60
  end
@@ -88,6 +88,13 @@ RSpec.describe RubySMB::GenericPacket do
88
88
  packet = RubySMB::SMB2::Packet::NegotiateResponse.read(smb2_error_packet.to_binary_s)
89
89
  expect(packet.original_command).to eq RubySMB::SMB2::Packet::NegotiateResponse::COMMAND
90
90
  end
91
+
92
+ context 'when the server returns an SMB1 error packet' do
93
+ let(:smb1_error_packet) { RubySMB::SMB1::Packet::EmptyPacket.new }
94
+ it 'returns the empty packet instead of the asked for class' do
95
+ expect(RubySMB::SMB2::Packet::NegotiateResponse.read(smb1_error_packet.to_binary_s)).to be_a RubySMB::SMB1::Packet::EmptyPacket
96
+ end
97
+ end
91
98
  end
92
99
  end
93
100
 
@@ -2,8 +2,7 @@ RSpec.describe RubySMB::Nbss::SessionHeader do
2
2
  subject(:session_header) { described_class.new }
3
3
 
4
4
  it { is_expected.to respond_to :session_packet_type }
5
- it { is_expected.to respond_to :flags }
6
- it { is_expected.to respond_to :packet_length }
5
+ it { is_expected.to respond_to :stream_protocol_length }
7
6
 
8
7
  it 'is big endian' do
9
8
  expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :big
@@ -15,15 +14,9 @@ RSpec.describe RubySMB::Nbss::SessionHeader do
15
14
  end
16
15
  end
17
16
 
18
- describe '#flags' do
19
- it 'is a 7-bit Unsigned Integer' do
20
- expect(session_header.flags).to be_a BinData::Bit7
21
- end
22
- end
23
-
24
- describe '#packet_length' do
25
- it 'is a 17-bit Unsigned Integer' do
26
- expect(session_header.packet_length).to be_a BinData::Bit17
17
+ describe '#stream_protocol_length' do
18
+ it 'is a 24-bit Unsigned Integer' do
19
+ expect(session_header.stream_protocol_length).to be_a BinData::Uint24be
27
20
  end
28
21
  end
29
22
  end
@@ -473,7 +473,7 @@ RSpec.describe RubySMB::SMB1::File do
473
473
  end
474
474
 
475
475
  it 'sets the File Information #rename_pending field of the packet' do
476
- expect(file.rename_packet(filename).data_block.trans2_data.info_level_struct.file_name).to eq filename.encode('utf-16le').force_encoding('ASCII-8BIT')
476
+ expect(file.rename_packet(filename).data_block.trans2_data.info_level_struct.file_name).to eq filename
477
477
  end
478
478
 
479
479
  it 'sets the Trans2 ParameterBlock fields' do
@@ -521,9 +521,7 @@ RSpec.describe RubySMB::SMB1::File do
521
521
 
522
522
  it 'raises an InvalidPacket exception if the response is not valid' do
523
523
  allow(response).to receive(:valid?).and_return(false)
524
- smb_header = double('SMB Header')
525
- allow(response).to receive(:smb_header).and_return(smb_header)
526
- allow(smb_header).to receive_messages(:protocol => nil, :command => nil)
524
+ allow(response).to receive(:packet_smb_version)
527
525
  expect { file.close }.to raise_error(RubySMB::Error::InvalidPacket)
528
526
  end
529
527
 
@@ -32,7 +32,6 @@ RSpec.describe RubySMB::SMB1::Packet::Trans2::FindFirst2Response do
32
32
  describe '#data_block' do
33
33
  subject(:data_block) { packet.data_block }
34
34
 
35
- it { is_expected.to respond_to :name }
36
35
  it { is_expected.to respond_to :trans2_parameters }
37
36
  it { is_expected.to respond_to :trans2_data }
38
37
 
@@ -32,7 +32,6 @@ RSpec.describe RubySMB::SMB1::Packet::Trans2::FindNext2Response do
32
32
  describe '#data_block' do
33
33
  subject(:data_block) { packet.data_block }
34
34
 
35
- it { is_expected.to respond_to :name }
36
35
  it { is_expected.to respond_to :trans2_parameters }
37
36
  it { is_expected.to respond_to :trans2_data }
38
37
 
@@ -35,16 +35,11 @@ RSpec.describe RubySMB::SMB1::Packet::Trans2::Open2Response do
35
35
  end
36
36
 
37
37
  it { is_expected.to respond_to :trans2_parameters }
38
- it { is_expected.to respond_to :trans2_data }
39
38
 
40
39
  it 'should keep #trans2_parameters 4-byte aligned' do
41
40
  expect(data_block.trans2_parameters.abs_offset % 4).to eq 0
42
41
  end
43
42
 
44
- it 'should keep #trans2_data 4-byte aligned' do
45
- expect(data_block.trans2_data.abs_offset % 4).to eq 0
46
- end
47
-
48
43
  describe '#trans2_parameters' do
49
44
  subject(:parameters) { data_block.trans2_parameters }
50
45
 
@@ -28,18 +28,12 @@ RSpec.describe RubySMB::SMB1::Packet::Trans2::SetFileInformationResponse do
28
28
  describe '#data_block' do
29
29
  subject(:data_block) { packet.data_block }
30
30
 
31
- it { is_expected.to respond_to :name }
32
31
  it { is_expected.to respond_to :trans2_parameters }
33
- it { is_expected.to respond_to :trans2_data }
34
32
 
35
33
  it 'should keep #trans2_parameters 4-byte aligned' do
36
34
  expect(data_block.trans2_parameters.abs_offset % 4).to eq 0
37
35
  end
38
36
 
39
- it 'should keep #trans2_data 4-byte aligned' do
40
- expect(data_block.trans2_data.abs_offset % 4).to eq 0
41
- end
42
-
43
37
  describe '#trans2_parameters' do
44
38
  subject(:parameters) { data_block.trans2_parameters }
45
39
 
@@ -80,9 +80,7 @@ RSpec.describe RubySMB::SMB1::Pipe do
80
80
 
81
81
  it 'raises an InvalidPacket exception if the response is not valid' do
82
82
  allow(response).to receive(:valid?).and_return(false)
83
- smb_header = double('SMB Header')
84
- allow(response).to receive(:smb_header).and_return(smb_header)
85
- allow(smb_header).to receive_messages(:protocol => nil, :command => nil)
83
+ allow(response).to receive(:packet_smb_version)
86
84
  expect { pipe.peek }.to raise_error(RubySMB::Error::InvalidPacket)
87
85
  end
88
86
 
@@ -149,12 +147,40 @@ RSpec.describe RubySMB::SMB1::Pipe do
149
147
  end
150
148
  end
151
149
 
150
+ context 'with \'\\srvsvc\' filename' do
151
+ it 'extends Srvsvc class' do
152
+ pipe = described_class.new(tree: tree, response: nt_create_andx_response, name: '\\srvsvc')
153
+ expect(pipe.respond_to?(:net_share_enum_all)).to be true
154
+ end
155
+ end
156
+
152
157
  context 'with \'winreg\' filename' do
153
158
  it 'extends Winreg class' do
154
159
  pipe = described_class.new(tree: tree, response: nt_create_andx_response, name: 'winreg')
155
160
  expect(pipe.respond_to?(:has_registry_key?)).to be true
156
161
  end
157
162
  end
163
+
164
+ context 'with \'\\winreg\' filename' do
165
+ it 'extends Winreg class' do
166
+ pipe = described_class.new(tree: tree, response: nt_create_andx_response, name: '\\winreg')
167
+ expect(pipe.respond_to?(:has_registry_key?)).to be true
168
+ end
169
+ end
170
+
171
+ context 'with \'svcctl\' filename' do
172
+ it 'extends svcctl class' do
173
+ pipe = described_class.new(tree: tree, response: nt_create_andx_response, name: 'svcctl')
174
+ expect(pipe.respond_to?(:query_service_config)).to be true
175
+ end
176
+ end
177
+
178
+ context 'with \'\\svcctl\' filename' do
179
+ it 'extends svcctl class' do
180
+ pipe = described_class.new(tree: tree, response: nt_create_andx_response, name: '\\svcctl')
181
+ expect(pipe.respond_to?(:query_service_config)).to be true
182
+ end
183
+ end
158
184
  end
159
185
 
160
186
  describe '#dcerpc_request' do
@@ -238,8 +264,7 @@ RSpec.describe RubySMB::SMB1::Pipe do
238
264
 
239
265
  context 'when the response is not a Trans packet' do
240
266
  it 'raises an InvalidPacket exception' do
241
- allow(trans_nmpipe_response).to receive_message_chain(:smb_header, :protocol)
242
- allow(trans_nmpipe_response).to receive_message_chain(:smb_header, :command)
267
+ allow(trans_nmpipe_response).to receive(:packet_smb_version)
243
268
  allow(trans_nmpipe_response).to receive(:valid?).and_return(false)
244
269
  expect { pipe.dcerpc_request(stub_packet, options) }.to raise_error(RubySMB::Error::InvalidPacket)
245
270
  end
@@ -492,4 +492,26 @@ RSpec.describe RubySMB::SMB1::Tree do
492
492
  end
493
493
  end
494
494
 
495
+ describe '#open_pipe' do
496
+ let(:opts) { { filename: 'test', write: true } }
497
+ before :example do
498
+ allow(tree).to receive(:open_file)
499
+ end
500
+
501
+ it 'calls #open_file with the provided options' do
502
+ opts[:filename] ='\\test'
503
+ expect(tree).to receive(:open_file).with(opts)
504
+ tree.open_pipe(opts)
505
+ end
506
+
507
+ it 'prepends the filename with \\ if needed' do
508
+ expect(tree).to receive(:open_file).with( { filename: '\\test', write: true } )
509
+ tree.open_pipe(opts)
510
+ end
511
+
512
+ it 'does not modify the original option hash' do
513
+ tree.open_pipe(opts)
514
+ expect(opts).to eq( { filename: 'test', write: true } )
515
+ end
516
+ end
495
517
  end
@@ -107,6 +107,10 @@ RSpec.describe RubySMB::SMB2::File do
107
107
  it 'sets the offset of the packet' do
108
108
  expect(file.read_packet(offset: 55).offset).to eq 55
109
109
  end
110
+
111
+ it 'sets the credit_charge of the packet' do
112
+ expect(file.read_packet(credit_charge: 3).smb2_header.credit_charge).to eq 3
113
+ end
110
114
  end
111
115
 
112
116
  describe '#read' do
@@ -125,7 +129,7 @@ RSpec.describe RubySMB::SMB2::File do
125
129
  end
126
130
 
127
131
  it 'uses a single packet to read the entire file' do
128
- expect(file).to receive(:read_packet).with(read_length: 108, offset: 0).and_return(small_read)
132
+ expect(file).to receive(:read_packet).with(read_length: 108, offset: 0, credit_charge: 0).and_return(small_read)
129
133
  expect(client).to receive(:send_recv).with(small_read, encrypt: false).and_return 'fake data'
130
134
  expect(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).with('fake data').and_return(small_response)
131
135
  expect(file.read).to eq 'fake data'
@@ -161,14 +165,15 @@ RSpec.describe RubySMB::SMB2::File do
161
165
  }
162
166
 
163
167
  before :example do
168
+ client.server_supports_multi_credit = 1
164
169
  allow(file).to receive(:read_packet)
165
170
  allow(client).to receive(:send_recv)
166
171
  allow(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).and_return(big_response)
167
172
  end
168
173
 
169
- it 'uses a multiple packet to read the file in chunks' do
170
- expect(file).to receive(:read_packet).once.with(read_length: described_class::MAX_PACKET_SIZE, offset: 0).and_return(big_read)
171
- expect(file).to receive(:read_packet).once.with(read_length: described_class::MAX_PACKET_SIZE, offset: described_class::MAX_PACKET_SIZE).and_return(big_read)
174
+ it 'uses multiple packets to read the file in chunks' do
175
+ expect(file).to receive(:read_packet).once.with(read_length: described_class::MAX_PACKET_SIZE, offset: 0, credit_charge: 1).and_return(big_read)
176
+ expect(file).to receive(:read_packet).once.with(read_length: described_class::MAX_PACKET_SIZE, offset: described_class::MAX_PACKET_SIZE, credit_charge: 1).and_return(big_read)
172
177
  expect(client).to receive(:send_recv).twice.and_return 'fake data'
173
178
  expect(RubySMB::SMB2::Packet::ReadResponse).to receive(:read).twice.with('fake data').and_return(big_response)
174
179
  file.read(bytes: (described_class::MAX_PACKET_SIZE * 2))
@@ -184,7 +189,7 @@ RSpec.describe RubySMB::SMB2::File do
184
189
 
185
190
  context 'when the second response is not valid' do
186
191
  it 'raise an InvalidPacket exception' do
187
- allow(file).to receive(:read_packet).with(read_length: described_class::MAX_PACKET_SIZE, offset: described_class::MAX_PACKET_SIZE) do
192
+ allow(file).to receive(:read_packet).with(read_length: described_class::MAX_PACKET_SIZE, offset: described_class::MAX_PACKET_SIZE, credit_charge: 1) do
188
193
  big_response.smb2_header.command = RubySMB::SMB2::Commands::LOGOFF
189
194
  end
190
195
  expect { file.read(bytes: (described_class::MAX_PACKET_SIZE * 2)) }.to raise_error(RubySMB::Error::InvalidPacket)
@@ -193,12 +198,31 @@ RSpec.describe RubySMB::SMB2::File do
193
198
 
194
199
  context 'when the second response status code is not STATUS_SUCCESS' do
195
200
  it 'raise an UnexpectedStatusCode exception' do
196
- allow(file).to receive(:read_packet).with(read_length: described_class::MAX_PACKET_SIZE, offset: described_class::MAX_PACKET_SIZE) do
201
+ allow(file).to receive(:read_packet).with(read_length: described_class::MAX_PACKET_SIZE, offset: described_class::MAX_PACKET_SIZE, credit_charge: 1) do
197
202
  big_response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
198
203
  end
199
204
  expect { file.read(bytes: (described_class::MAX_PACKET_SIZE * 2)) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
200
205
  end
201
206
  end
207
+
208
+ context 'when the server does not support multi credits' do
209
+ it 'reads 65536 bytes at a time with no credit charge' do
210
+ client.server_supports_multi_credit = false
211
+ expect(file).to receive(:read_packet).once.with(read_length: 65536, offset: 0, credit_charge: 0).and_return(big_read)
212
+ expect(file).to receive(:read_packet).once.with(read_length: 65536, offset: 65536, credit_charge: 0).and_return(big_read)
213
+ file.read(bytes: (described_class::MAX_PACKET_SIZE * 4))
214
+ end
215
+ end
216
+
217
+ context 'when the server supports multi credits' do
218
+ it 'reads a number of bytes equal to #server_max_read_size, with the expected credit charge' do
219
+ credit_charge = (90000 - 1) / 65536 + 1
220
+ client.server_max_read_size = 90000
221
+ expect(file).to receive(:read_packet).once.with(read_length: 90000, offset: 0, credit_charge: credit_charge).and_return(big_read)
222
+ expect(file).to receive(:read_packet).once.with(read_length: (described_class::MAX_PACKET_SIZE * 4 - 90000), offset: 90000, credit_charge: credit_charge).and_return(big_read)
223
+ file.read(bytes: (described_class::MAX_PACKET_SIZE * 4))
224
+ end
225
+ end
202
226
  end
203
227
  end
204
228
 
@@ -222,6 +246,10 @@ RSpec.describe RubySMB::SMB2::File do
222
246
  it 'sets the buffer on the packet' do
223
247
  expect(file.write_packet(data:'hello').buffer).to eq 'hello'
224
248
  end
249
+
250
+ it 'sets the credit_charge on the packet header' do
251
+ expect(file.write_packet(credit_charge: 5).smb2_header.credit_charge).to eq 5
252
+ end
225
253
  end
226
254
 
227
255
  describe '#write' do
@@ -242,6 +270,11 @@ RSpec.describe RubySMB::SMB2::File do
242
270
  end
243
271
 
244
272
  context 'for a large write' do
273
+ before :example do
274
+ allow(client).to receive(:send_recv).and_return(write_response.to_binary_s)
275
+ client.server_supports_multi_credit = 1
276
+ end
277
+
245
278
  it 'sends multiple packets' do
246
279
  expect(client).to receive(:send_recv).twice.and_return(write_response.to_binary_s)
247
280
  file.write(data: SecureRandom.random_bytes(described_class::MAX_PACKET_SIZE + 1))
@@ -254,6 +287,27 @@ RSpec.describe RubySMB::SMB2::File do
254
287
  expect(client).to receive(:send_recv).twice.with(write_request, encrypt: true).and_return(write_response.to_binary_s)
255
288
  file.write(data: SecureRandom.random_bytes(described_class::MAX_PACKET_SIZE + 1))
256
289
  end
290
+
291
+ context 'when the server does not support multi credits' do
292
+ it 'writes 65536 bytes at a time with no credit charge' do
293
+ client.server_supports_multi_credit = false
294
+ data = SecureRandom.random_bytes(65536 * 2)
295
+ expect(file).to receive(:write_packet).once.with(data: data[0, 65536], offset: 0, credit_charge: 0)
296
+ expect(file).to receive(:write_packet).once.with(data: data[65536, (data.size - 65536)], offset: 65536, credit_charge: 0)
297
+ file.write(data: data)
298
+ end
299
+ end
300
+
301
+ context 'when the server supports multi credits' do
302
+ it 'reads a number of bytes equal to #server_max_write_size, with the expected credit charge' do
303
+ data = SecureRandom.random_bytes(90000 * 2)
304
+ credit_charge = (90000 - 1) / 65536 + 1
305
+ client.server_max_write_size = 90000
306
+ expect(file).to receive(:write_packet).once.with(data: data[0, 90000], offset: 0, credit_charge: credit_charge)
307
+ expect(file).to receive(:write_packet).once.with(data: data[90000, (data.size - 90000)], offset: 90000, credit_charge: credit_charge)
308
+ file.write(data: data)
309
+ end
310
+ end
257
311
  end
258
312
 
259
313
  it 'raises an InvalidPacket exception if the response is not valid' do
@@ -405,9 +459,7 @@ RSpec.describe RubySMB::SMB2::File do
405
459
 
406
460
  it 'raises an InvalidPacket exception if the response is not valid' do
407
461
  allow(response).to receive(:valid?).and_return(false)
408
- smb2_header = double('SMB2 Header')
409
- allow(response).to receive(:smb2_header).and_return(smb2_header)
410
- allow(smb2_header).to receive_messages(:protocol => nil, :command => nil)
462
+ allow(response).to receive(:packet_smb_version)
411
463
  expect { file.close }.to raise_error(RubySMB::Error::InvalidPacket)
412
464
  end
413
465
 
@@ -86,9 +86,7 @@ RSpec.describe RubySMB::SMB2::Pipe do
86
86
 
87
87
  it 'raises an InvalidPacket exception if the response is not valid' do
88
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)
89
+ allow(response).to receive(:packet_smb_version)
92
90
  expect { pipe.peek }.to raise_error(RubySMB::Error::InvalidPacket)
93
91
  end
94
92
 
@@ -161,6 +159,13 @@ RSpec.describe RubySMB::SMB2::Pipe do
161
159
  expect(pipe.respond_to?(:has_registry_key?)).to be true
162
160
  end
163
161
  end
162
+
163
+ context 'with \'svcctl\' filename' do
164
+ it 'extends svcctl class' do
165
+ pipe = described_class.new(tree: tree, response: create_response, name: 'svcctl')
166
+ expect(pipe.respond_to?(:query_service_config)).to be true
167
+ end
168
+ end
164
169
  end
165
170
 
166
171
  describe '#dcerpc_request' do
@@ -254,8 +259,7 @@ RSpec.describe RubySMB::SMB2::Pipe do
254
259
 
255
260
  context 'when the response is not an IoctlResponse packet' do
256
261
  it 'raises an InvalidPacket exception' do
257
- allow(ioctl_response).to receive_message_chain(:smb2_header, :protocol)
258
- allow(ioctl_response).to receive_message_chain(:smb2_header, :command)
262
+ allow(ioctl_response).to receive(:packet_smb_version)
259
263
  allow(ioctl_response).to receive(:valid?).and_return(false)
260
264
  expect { pipe.ioctl_send_recv(dcerpc_request, options) }.to raise_error(RubySMB::Error::InvalidPacket)
261
265
  end